隊列
Ⅰ 隊列的定義
隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱爲隊尾,進行刪除操作的端稱爲隊頭。
隊列的特點:先進先出。
堆棧的特點:先進後出。
Ⅱ 隊列的實現
隊列有兩種實現的方法,鏈表和數組。對於隊列來說,用鏈表是很麻煩的,爲了提高效率和簡化操作,隊列還是用數組來實現比較好。
A. 普通數組的不合理之處
我先建立一個數組。
在還沒有數據存入的時候,隊首隊尾指針都在下標爲0的位置,當入隊列時,數據存入隊首指針現在的位置,並且隊首指針後移。
這時候再出隊列,隊尾指針指向的位置上的元素出去。
在這個過程中,隊首和隊尾的指針都是不斷增大的,但是數組的個數是有限的。
導致即使前面的數據都已出隊列,數組還有這麼多空間,但是無法使用,因爲隊尾隊首指針在不斷向前。
B. 循環數組的應用
在普通的數組不能很好地完成隊列操作時,我在此引入循環數組。
循環數組我們可以用下圖來理解,類似於一個鐘錶:
當入隊列時,
這就實現了從隊首出隊列,從隊尾入隊列。
還是剛纔普通數組難以解決的問題,當頭指針在12,尾指針在13,這時候入隊列需要將其放在尾指針指向的位置,並將尾指針+1.
但是此時13+1需要將尾指針變成0,要怎麼做?
這就是循環數組的精妙之處了。以這個例子來看,這是個容量爲14的數組。我們設下標爲index,容量爲capacity。
(index + 1) % capacity就可以做到將下標增加,並使其循環。
以剛纔爲例,現在尾指針爲13,要入隊列,先將數據放在尾指針的位置,即13,再將尾指針(13 + 1) % 14 = 0,這就實現了循環。
那麼數組裏的有效數據個數怎麼計算呢?我們來看下面兩個例子。
這兩種情況,剛好是head和tail的值顛倒過來,如果我們要計算有效數據個數,就需要一個公式:(tail - head + capacity) % capacity。
但是這時候我們會發現,當隊列滿和隊列空的時候,計算出來的數據個數都是0。
所以要判斷隊列空還是滿,我們還需要再加入一個判斷,即上一次操作是入隊列還是出隊列。
如果上一次操作是入隊列,那麼如果計算出來數據個數爲0,就可以判斷這個隊列是滿的,反之上一次操作是出隊列,那麼隊列就是空的。
Ⅲ 隊列工具庫的創建
循環數組的規律清楚了以後,我們就可以動手來實現隊列的工具庫了。
A. 隊列的表示
首先我們先來表示隊列。在之前的分析中,可以知道,要建立一個隊列,需要隊首指針head,需要隊尾指針tail,要建立數組我們還需要知道數組的大小capacity,然後我們還需要知道上一次操作是什麼,所以衆多的元素我們需要建立一個結構體。
typedef struct QUEUE {
int head;
int tail;
int capacity;
void **data;
boolean lastAction;
}QUEUE;
這樣我們就建立了一個隊列的控制頭。大家可能會對這個void **data
有點疑惑。爲什麼要這麼寫呢?
這樣其實是建立了一個數組,其存放的元素是指針。因爲我們不知道用戶到底要在隊列裏錄入什麼類型的數據,所以我們無論其錄入什麼數據,我們都將其地址存入數組中,這樣就可以實現通用數組。因爲無論你的數據是什麼類型,幾個字節,其地址都是四個字節,所以可以通過存放地址,來保證數組的元素類型統一。這其實就是一個指針數組。
關於指針數組有疑惑的同學也可以去看我下面這篇文章👇
【C語言基礎】->指針與二維數組->同一個人的換裝遊戲
另外還有一個說明,boolean
是我自己在我的頭文件中定義的一個類型,其本質是unsigned char
。
#ifndef _TYZ_QUEUE_H_
#define _TYZ_QUEUE_H_
#include "tyz.h"
#define IN 2
#define OUT 3
typedef struct QUEUE {
int head;
int tail;
int capacity;
void **data;
boolean lastAction;
}QUEUE;
#endif
通過對IN
和OUT
的宏定義,我們來判斷上一次操作到底是什麼。
B. 初始化&銷燬
a. 初始化
要使用隊列,最開始當然要先初始化。代碼如下👇
boolean initQueue(QUEUE **queue, int capacity) {
if (NULL == queue || NULL != *queue || capacity <= 0) {
return FALSE;
}
*queue = (QUEUE *) calloc(sizeof(QUEUE), 1);
if (NULL == queue) {
return FALSE;
}
(*queue)->data = (void **) calloc(sizeof(void *), capacity);
if (NULL == (*queue)->data) {
free(*queue);
*queue = NULL;
return FALSE;
}
(*queue)->head = 0;
(*queue)->tail = 0;
(*queue)->capacity = capacity;
(*queue)->lastAction = OUT;
return TRUE;
}
可能會有人對我的形參有點疑惑,爲什麼是指針的指針。QUEUE **queue
。我在下面列出調用這個函數的代碼做爲比較。
QUEUE *queue = NULL;;
initQueue(&queue, 30);
可以看到我們先定義了一個指針,指向控制頭QUEUE
,根據一個著名的例子,用指針交換兩個變量的值,如果你要改變實參的值,傳遞形參就應該把它的地址傳過去,否則是無法改變它的值的。所以參考這裏,我們需要初始化這個QUEUE *queue
,就需要傳遞它的地址,即&queue
。其對應的形參就是QUEUE **queue
了。
b. 銷燬
申請了空間首先就要考慮釋放空間,所以接下來要寫的就是銷燬隊列。👇
void destoryQueue(QUEUE **queue) {
if (NULL == queue || NULL != *queue) {
return;
}
free((*queue)->data);
free(*queue);
*queue = NULL;
}
這裏的原理是一樣的,要傳遞(QUEUE **queue)
。因爲要銷燬指向控制頭的那個指針。
C. 判斷隊列空&隊列滿
在之前的分析中,我們已經知道了如何判斷空滿,只需要滿足兩個條件,一個是head
和tail
值相等,一個是上一次操作是入隊列還是出隊列。
a. 判斷隊列滿
boolean isQueneFull(const QUEUE *queue) {
if (NULL == queue) {
return TRUE;
}
return IN == queue->lastAction && queue->head == queue->tail;
}
如果傳遞進去的是一個無效的指針,那麼我將其看做的滿的。因爲只有要入隊列的時候,需要判斷隊列是不是滿的,這時候無效的指針是無法將數據入隊列的,所以我將其看做是滿的,意思也是不能再入隊列了。
b. 判斷隊列空
boolean isQueneEmpty(const QUEUE *queue) {
if (NULL == queue) {
return TRUE;
}
return OUT == queue->lastAction && queue->head == queue->tail;
}
遵從和上面一樣的原則。
D. 入隊列&出隊列
a. 入隊列
boolean in(QUEUE *queue, void *data) {
if (NULL == queue || isQueneFull(queue)) {
return FALSE;
}
queue->data[queue->tail] = data;
queue->tail = (queue->tail + 1) % queue->capacity;
queue->lastAction = IN;
return TRUE;
}
這個邏輯就是之前分析的,從尾指針的地方入隊列,並將尾指針向前走一格,方式就是(queue->tail + 1) % queue->capacity
。
b. 出隊列
boolean out(QUEUE *queue, void *data) {
if (NULL == queue || isQueneEmpty(queue)) {
return FALSE;
}
data = queue->data[queue->head];
queue->head = (queue->head + 1) % queue->capacity;
queue->lastAction = OUT;
}
其邏輯是一樣的,從隊首出去,然後將頭指針向前挪動一格。
出隊列和入隊列都是很簡單的,需要注意的就是要將lastAction
設置爲對應的IN
或OUT
。
E. 讀取隊首數據
讀取隊首數據是和出隊列差不多的,只是不需要將頭指針挪動。
void readHead(const QUEUE *queue, void *data) {
if (NULL == queue || isQueneEmpty(queue)) {
return;
}
data = queue->data[queue->head];
}
這樣我們就完成了一個隊列的工具庫,其基本功能就是入隊列和出隊列。
Ⅳ. 完整代碼
首先是我的自己的頭文件tyz.h👇
#ifndef _TYZ_H_
#define _TYZ_H_
#define TRUE 1
#define FALSE 0
#define NOT_FOUND -1
#define SET(value, index) (value |= 1 << (index ^ 7))
#define CLEAR(value, index) (value &= ~(1 << (index ^ 7)))
#define GET(value, index) ((value) & (1 << (index ^ 7)) != 0)
typedef unsigned char boolean;
typedef unsigned int u32;
typedef unsigned short u16;
typedef boolean u8;
int skipBlank(const char *str);
boolean isRealStart(int ch);
#endif
然後是隊列的頭文件,queue.h👇
#ifndef _TYZ_QUEUE_H_
#define _TYZ_QUEUE_H_
#include "tyz.h"
#define IN 2
#define OUT 3
typedef struct QUEUE {
int head;
int tail;
int capacity;
void **data;
boolean lastAction;
}QUEUE;
boolean initQueue(QUEUE **queue, int capacity);
void destoryQueue(QUEUE **queue);
boolean isQueneFull(const QUEUE *queue);
boolean isQueneEmpty(const QUEUE *queue);
boolean in(QUEUE *queue, void *data);
boolean out(QUEUE *queue, void *data);
void readHead(const QUEUE *queue, void *data);
#endif
最後是隊列的函數部分代碼👇
#include <stdio.h>
#include <malloc.h>
#include "tyz.h"
#include "queue.h"
void readHead(const QUEUE *queue, void *data) {
if (NULL == queue || isQueneEmpty(queue)) {
return;
}
data = queue->data[queue->head];
}
boolean out(QUEUE *queue, void *data) {
if (NULL == queue || isQueneEmpty(queue)) {
return FALSE;
}
data = queue->data[queue->head];
queue->head = (queue->head + 1) % queue->capacity;
queue->lastAction = OUT;
}
boolean in(QUEUE *queue, void *data) {
if (NULL == queue || isQueneFull(queue)) {
return FALSE;
}
queue->data[queue->tail] = data;
queue->tail = (queue->tail + 1) % queue->capacity;
queue->lastAction = IN;
return TRUE;
}
boolean isQueneEmpty(const QUEUE *queue) {
if (NULL == queue) {
return TRUE;
}
return OUT == queue->lastAction && queue->head == queue->tail;
}
boolean isQueneFull(const QUEUE *queue) {
if (NULL == queue) {
return TRUE;
}
return IN == queue->lastAction && queue->head == queue->tail;
}
void destoryQueue(QUEUE **queue) {
if (NULL == queue || NULL != *queue) {
return;
}
free((*queue)->data);
free(*queue);
*queue = NULL;
}
boolean initQueue(QUEUE **queue, int capacity) {
if (NULL == queue || NULL != *queue || capacity <= 0) {
return FALSE;
}
*queue = (QUEUE *) calloc(sizeof(QUEUE), 1);
if (NULL == queue) {
return FALSE;
}
(*queue)->data = (void **) calloc(sizeof(void *), capacity);
if (NULL == (*queue)->data) {
free(*queue);
*queue = NULL;
return FALSE;
}
(*queue)->head = 0;
(*queue)->tail = 0;
(*queue)->capacity = capacity;
(*queue)->lastAction = OUT;
return TRUE;
}
這樣我們就完成了隊列工具庫的創建。
如果對指針的操作還有疑問的可以看我下面的文章👇
【C語言基礎】->指針與二維數組->同一個人的換裝遊戲