佇列的順序儲存結構和鏈式儲存結構
佇列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表(在隊尾進行插入操作,在對頭進行刪除操作)。
與棧相反,佇列是一種先進先出(First In First Out, FIFO)的線性表。
與棧相同的是,佇列也是一種重要的線性結構,實現一個佇列同樣需要順序表或連結串列作為基礎。
佇列的鏈式儲存結構
佇列既可以用連結串列實現,也可以用順序表實現。跟棧相反的是,棧一般我們用順序表來實現,而佇列我們常用連結串列來實現,簡稱為鏈佇列。
typedef struct QNode { ElemType data; struct QNode *next; } QNode, *QueuePrt;</span>
<span style="font-family:KaiTi_GB2312;font-size:18px;"> typedef struct {
QueuePrt front; //隊頭
QueuePrt rear; //隊尾指標
} LinkQueue;
將隊頭指標指向鏈佇列的頭結點,而隊尾指標指向終端結點。(注:頭結點不是必要的,但為了方便操作,我們加上了。)
佇列的鏈式儲存結構
空佇列時,front和rear都指向頭結點。
建立一個佇列
建立一個佇列要完成兩個任務:一是在記憶體中建立一個頭結點,二是將佇列的頭指標和尾指標都指向這個生成的頭結點,因為此時是空佇列。
initQueue(LinkQueue *q){ q->front=q->rear=(QueuePtr)malloc(sizeof(QNode)); if( !q->front ) exit(0); q->front->next = NULL; }
入佇列操作
//入佇列操作
InsertQueue(LinkQueue *q, ElemType e){
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode));
if( p == NULL )
exit(0);
p->data = e;
p->next = NULL;
q->rear->next = p;
q->rear = p;
}
出佇列操作
出佇列操作是將佇列中的第一個元素移出,隊頭指標不發生改變,改變頭結點的next指標即可。
如果原佇列只有一個元素,那麼我們就應該處理一下隊尾指標。
出佇列操作
DeleteQueue(LinkQueue *q, ELemType *e){
QueuePtr p;
if( q->front == q->rear )
return;
p = q->front->next;
*e = p->data;
q->front->next = p->next;
if( q->rear == p )
q->rear = q->front;
free(p);
}
銷燬一個佇列
由於鏈佇列建立在記憶體的動態區,因此當一個佇列不再有用時應當把它及時銷燬掉,以免過多地佔用記憶體空間。
DestroyQueue(LinkQueue *q){
while( q->front )
{
q->rear = q->front->next;
free( q->front );
q->front = q->rear;
}
}
佇列的順序儲存結構
我們假設一個佇列有n個元素,則順序儲存的佇列需建立一個大於n的儲存單元,並把佇列的所有元素儲存在陣列的前n個單元,陣列下標為0的一端則是隊頭。
入佇列操作其實就是在隊尾追加一個元素,不需要任何移動,時間複雜度為O(1)。
出佇列則不同,因為我們已經架設下標為0的位置是佇列的隊頭,因此每次出佇列操作所有元素都要向前移動。
在現實中也是如此,一群人在排隊買火車票,前邊的人買好了離開,後面的人就要全部向前一步補上空位。
可是我們研究資料結構和演算法的一個根本目的就是要想方設法提高我們的程式的效率,按剛才的方式,出佇列的時間複雜度是O(n),效率大打折扣!
如果我們不去限制隊頭一定要在下標為0的位置,那麼出佇列的操作就不需要移動全體元素。
但是這樣也會出現一些問題,例如按下邊的情形繼續入佇列,就會出現陣列越界的錯誤。
可事實上我們有0和1兩個下標還空著,這叫假溢位。
迴圈佇列(在實際中應用相當廣泛)
我們再想想,要解決假溢位的辦法就是如果後面滿了,就再從頭開始,也就是頭尾相接的迴圈。
迴圈佇列它的容量是固定的,並且它的隊頭和隊尾指標都可以隨著元素入、出佇列而發生改變,這樣迴圈佇列邏輯上就好像是一個環形儲存空間。
但要注意的是,在實際的記憶體當中,不可能有真正的環形儲存區,我們只是用順序表模擬出來的邏輯上的迴圈。
於是我們發覺了,似乎迴圈佇列的實現只需要靈活改變front和rear指標即可。
也就是讓front或rear指標不斷加1,即使超出了地址範圍,也會自動從頭開始。我們可以採取取模運算處理:(這個運算在資料結構中特別常見,也特別重要!)
(rear+1) % QueueSize
(front+1) % QueueSize
取模就是取餘數的意思,他取到的值永遠不會大於除數。
定義一個迴圈佇列
#define MAXSIZE 100
typedef struct{
ElemType *base; // 用於存放記憶體分配基地址,這裡你也可以用陣列存放
int front;
int rear;
}
初始化一個迴圈佇列
initQueue(cycleQueue *q){
q->base = (ElemType *) malloc (MAXSIZE * sizeof(ElemType));
if( !q->base )
exit(0);
q->front = q->rear = 0;
}
入佇列操作
InsertQueue(cycleQueue *q, ElemType e){
if( (q->rear+1)%MAXSIZE == q->front )
return; // 佇列已滿
q->base[q->rear] = e;
q->rear = (q->rear+1) % MAXSIZE;
}
出佇列操作
DeleteQueue(cycleQueue *q, ElemType *e){
if( q->front == q->rear )
return ; // 佇列為空
*e = q->base[q->front];
q->front = (q->front+1) % MAXSIZE;
}