佇列——鏈佇列和迴圈佇列
鏈佇列
轉載:https://www.cnblogs.com/muzijie/p/5655228.html
1 鏈佇列的儲存結構
將對頭指標front指向鏈佇列的頭結點,隊尾指標rear指向終端結點。
空佇列時,頭指標front和尾指標rear都指向頭結點。
鏈佇列的儲存結構為:
typedef int QElemType; typedef struct QNode { //結點結構 QElemType data; struct QNode *next; }QNode; typedef struct QNode * QueuePtr; typedef struct { //佇列的連結串列結構 QueuePtr rear; QueuePtr front; }LinkQueue;
2 入隊操作
//插入元素e為Q的新的隊尾結點
Status EnQueue(QueuePtr Q, QElemType e) {
QueuePtr q = (QueuePtr)malloc(sizeof(QNode));
if (!q) { //儲存分配失敗
exit(OVERFLOW);
}
q->data = e;
q->next = NULL;
Q->rear->next = q;
Q->rear = q;
return OK;
}
3 出隊操作
出隊操作,就是頭結點的後繼結點出隊,將頭結點的後繼改為它後面的結點。
若連結串列除頭結點外只剩一個元素時,則需將rear指標指向頭結點。
//若佇列不空,刪除Q的隊頭元素,用e返回其值,並返回OK,否則返回ERROR。 Status DeQueue(QueuePtr Q, QElemType *e) { QueuePtr q; if (Q->rear == Q->front) { //空佇列 return ERROR; } q = Q->front->next; //q指向第一個結點 *e = q->data; Q->front->next = q->next; if (Q->rear == p) { //若隊頭就是隊尾,刪除後,需要將rear指標指向頭結點 Q->rear = Q->front; } free(q); return OK; }
4 迴圈佇列與鏈佇列的比較
從時間上考慮,迴圈佇列和鏈佇列的基本操作都是O(1),不過迴圈佇列是事先已申請好空間,使用期間不會釋放。而對於鏈佇列,每次申請和釋放結點也會存在一些時間開銷。如果入隊和出隊頻繁,兩者還是有細微差異的。
從空間來說,迴圈佇列必須有一個固定的長度,所以就有了儲存元素個數和空間浪費的問題。而鏈佇列不存在這個問題,儘管它需要一個指標域,會產生一些空間上的開銷,但是是可以接受的。所以從空間上說,鏈佇列更加靈活。
總的來說,在可以確定鏈佇列最大長度的情況下,建議使用迴圈佇列。如果無法預估佇列的長度,則使用鏈佇列。
迴圈佇列
轉載:https://www.cnblogs.com/hughdong/archive/2017/05/11/6841970.html
(作者說的挺有意思的話:You know something and I know nothing.)
和順序棧相類似,在佇列的順序儲存結構中,除了用一組地址連續的儲存單元依次存放從隊頭到隊尾的元素外,尚需敷設兩個指標front和rear分別指示佇列頭元素位置和佇列尾元素的位置。
如果使用順序表作為佇列的話,當處於右圖狀態則不能繼續插入新的隊尾元素,否則會因為陣列越界而導致程式程式碼被破壞。
由此產生了由連結串列實現的迴圈佇列,只有佇列未滿時才可以插入新的隊尾元素。
下面內容,轉載:https://www.cnblogs.com/chenliyang/p/6554141.html
1.圖中有兩個指標(其實就是兩個整數型變數,因為在這裡有指示作用,所以這裡理解為指標)front、rear,一個指示隊頭,一個指示隊尾。
2.rear和front互相追趕著,這個追趕過程就是佇列新增和刪除的過程,如果rear追到head說明佇列滿了,如果front追到rear說明佇列。
說明:令佇列空間中的一個單元閒置,使得佇列非空時,Q.rear與Q.front之間至少間隔一個空閒單。(思考為什麼空一格)
3.我們把它掰彎,用的是求餘,這樣兩個值就不會跑出最大範圍,並且可以實現彎曲的效果,所以說對於迴圈佇列我們必須給定最大值MAXQSIZE。
這其實是我們臆想的,反正我們要做的就是利用迴圈來解決空間浪費的問題。
迴圈佇列的實現過程(important)
☆當新增一個元素時,(rear+1)%MAXQSIZE; //理解為什麼求餘?
☆當刪除一個元素時,(front+1)%MAXQSIZE;//理解為什麼求餘?
☆當rear=front的時候,佇列可能是滿,也可能是空。(這也是為什麼空一格的原因)
因為存在滿和空兩種情況,我們需要分別判斷:
☆滿:當佇列新增元素到rear的下一個元素是head的時候,也就是轉圈子要碰頭了,我們就認為佇列滿了。(Q.rear+1)%MAXSIZE=Q.front
☆空:當佇列刪除元素到head=rear的時候,我們認為佇列空了。Q.rear==Q.front,不一定為0
上面這一段要好好理解,在其他程式設計的地方,也會用到類似的思想。
下面的程式碼思想很重要。
2.1對節點的定義
#define MAXQSIZE 100
typedef int QElemtype;
typedef int status;
typedef struct{
QElemtype *base;
int front;
int rear;
}SqQueue;
2.2初始化佇列
SqQueue* InitQueue()
{
SqQueue *q;
q=new SqQueue;
q->base=new int[MAXQSIZE];
q->rear=q->front=0;
return q;
}
2.3新增操作
status EnQueue(SqQueue *q,QElemtype e)
{
//插入到隊尾
if((q->rear+1)%MAXQSIZE==q->front)
return 0;
q->base[q->rear]=e;
q->rear=(q->rear+1)%MAXQSIZE;
return 1;
}
2.4刪除操作
status DeQueue(SqQueue *q)
{
if(q->front==q->rear)
return 0;
printf("%d",q->base[q->front]);
q->front =(q->front+1)%MAXQSIZE;
return 1;
}
備註,這插入和刪除的操作,類似於標記。(這也很重要)
2.5獲取佇列長度
int QueueLength(SqQueue *q)
{
return (q->rear-q->front+MAXQSIZE)%MAXQSIZE;
}
補:還有些其他的佇列,比如優先佇列,雙端佇列。(用的時候可以查STL)
佇列練習: