鏈隊列的存儲和入隊出隊操作


今天看圖的廣度優先遍歷的時候,發現用到了隊列,補一下鏈隊列的知識,參考《大話數據結構》的P118~120,自己寫了一個簡單的測試例子便於理解。

理解起來並不難,用的是單鏈表結構。front指向鏈表的頭結點(是虛結點,它的next指向第一個節點),rear指向鏈表的尾節點。

下面舉個簡單的例子,實現鏈隊列的創建,入隊和出隊操作。

 

第一個程序調試了很久,編譯沒有問題,運行總是崩潰。是對內存分配沒有考慮全面,先把錯誤的程序放上來思考一下。

 1 #include <iostream>
 2 #include <string>
 3 #include <stdlib.h>
 4 using namespace std;  5 #define OVERFLOW 0
 6 
 7 //鏈隊列的結點結構
 8 typedef struct QNode  9 { 10     char data; 11     struct QNode *next;//定義指向下一個結點的指針next
12 }QNode; 13 
14 //隊列的鏈式存儲結構
15 typedef struct
16 { 17     QNode *front,*rear;//定義指向隊頭結點和隊尾結點的指針
18 }LinkQueue; 19 
20 //入隊操作,在鏈表尾部插入結點
21 LinkQueue *EnQueue(LinkQueue *Q,char e) 22 { 23     Q=new LinkQueue;//或者Q=(LinkQueue*)malloc(sizeof(LinkQueue));
24     Q->front=Q->rear=NULL; 25     QNode *s;//定義一個指向結點的指針s
26     s=new QNode;//或者s=(QNode *)malloc(sizeof(QNode));
27     if(!s) exit(OVERFLOW);//如果分配結點內存失敗,直接結束程序 28     //exit函數的頭文件是stdlib.h。 29     //exit的聲明為void exit(int value); 30     //exit的作用是,退出程序,並將參數value的值返回給主調進程。 31     //exit(a); 等效於在main函數中return a; 32 
33     //給s指向的新結點填內容
34     s->data=e; 35     s->next=NULL; 36     Q->rear->next=s;//使rear指向結點的next指向新節點s
37     Q->rear=s;//使rear指向新節點s
38     cout<<"ok"<<" "; 39     return Q;//返回指向該鏈隊列的地址
40 } 41 
42 //出隊操作,把頭結點的next指向的元素出隊,返回出隊的元素
43 char DeQueue(LinkQueue *Q) 44 { 45     char e; 46     QNode *s;//定義一個指向結點的指針s
47     if(Q->front==Q->rear) return 0;//如果鏈隊列為空,返回0,也可以定義其他char類型的標志
48     s=Q->front->next;//把要刪除的結點的地址暫存給p
49     e=s->data;//把要刪除的結點的data給e,需要返回該元素
50     Q->front->next=s->next;//把要刪除的結點的下一個結點的地址給頭結點的next
51     if(Q->rear==s) 52         Q->rear=Q->front;//如果要刪除的結點是隊尾結點,說明刪除該結點后鏈隊列為空,rear和front同時指向頭結點
53     delete s;//或者frees);
54     return e;//返回出隊元素
55 } 56 
57 int main() 58 { 59     LinkQueue *p=NULL; 60     p=EnQueue(p,'A');//插入A
61     cout<<p->rear->data<<endl; 62 }

運行到第36行時候崩潰

通過思考,是因為沒有讓Q指向的鏈結構LinkQueue中的front和rear指針指向new出來的QNode。

 

修改后的程序可以正常運行,代碼和解釋如下(VS2012測試通過):

 1 #include <iostream>
 2 #include <string>
 3 #include <stdlib.h>
 4 using namespace std;  5 
 6 #define OVERFLOW 0
 7 
 8 //鏈隊列的結點結構
 9 typedef struct QNode 10 { 11     char data; 12     struct QNode *next;//定義指向下一個結點的指針next
13 }QNode; 14 
15 //隊列的鏈式存儲結構,可以看到只占8個字節(sizeof(LinkQueue)==8)
16 typedef struct
17 { 18     QNode *front,*rear;//定義指向隊頭結點和隊尾結點的指針
19 }LinkQueue; 20 
21 //鏈隊列的初始化,申請內存
22 LinkQueue *InitQueue(LinkQueue *Q) 23 { 24     Q=new LinkQueue;//或者Q=(LinkQueue*)malloc(sizeof(LinkQueue));
25     Q->front=Q->rear=new QNode; 26     //初始化為空隊列(front==rear表示空隊列),並且同時指向頭結點,如果只是置為相等,不指向new出來的QNode,程序會崩潰
27     return Q; 28 } 29 //為什么new一個頭結點,頭結點的next指向鏈隊列的第一個節點,可以思考一下如果沒有頭結點會怎樣? 30 
31 //入隊操作,在鏈表尾部插入結點
32 void EnQueue(LinkQueue *Q,char e) 33 { 34     QNode *s;//定義一個指向結點的指針s
35     s=new QNode;//或者s=(QNode *)malloc(sizeof(QNode));
36     if(!s) exit(OVERFLOW);//如果分配結點內存失敗,直接結束程序 37     //exit函數的頭文件是stdlib.h。 38     //exit的聲明為void exit(int value); 39     //exit的作用是,退出程序,並將參數value的值返回給主調進程。 40     //exit(a); 等效於在main函數中return a; 41 
42     //給s指向的新結點填內容
43     s->data=e; 44     s->next=NULL; 45     Q->rear->next=s;//使rear指向結點的next指向新節點s
46     Q->rear=s;//使rear指向新節點s
47     cout<<"ok"<<" "; 48 } 49 
50 //出隊操作,把頭結點的next指向的元素出隊,返回出隊的元素
51 char DeQueue(LinkQueue *Q) 52 { 53     char e; 54     QNode *s;//定義一個指向結點的指針s
55     if(Q->front==Q->rear) 56  { 57         cout<<"error"<<" "; 58         return 0;//如果鏈隊列為空,返回0,也可以定義其他char類型的標志
59  } 60     s=Q->front->next;//把要刪除的結點的地址暫存給p
61     e=s->data;//把要刪除的結點的data給e,需要返回該元素
62     Q->front->next=s->next;//把要刪除的結點的下一個結點的地址給頭結點的next
63     if(Q->rear==s) 64  { 65         Q->rear=Q->front;//如果要刪除的結點是隊尾結點,說明刪除該結點后鏈隊列為空,rear和front同時指向頭結點
66  } 67     delete s;//或者free(s);
68     cout<<"ok"<<" "<<e<<endl;//輸出出隊元素
69     return e;//返回出隊元素
70 } 71 
72 int main() 73 { 74     LinkQueue *p=NULL; 75     p=InitQueue(p);//如果缺少這一步程序就崩潰,一開始沒有注意到這個問題,花了一個多小時 76 
77     //入隊
78     cout<<"in:"<<endl; 79     EnQueue(p,'A');//插入A
80     cout<<p->rear->data<<endl;//輸出A
81     EnQueue(p,'B');//插入B
82     cout<<p->rear->data<<endl;//輸出B
83     EnQueue(p,'C');//插入C
84     cout<<p->rear->data<<endl;//輸出C
85     EnQueue(p,'D');//插入D
86     cout<<p->rear->data<<endl;//輸出D 87 
88     //出隊
89     cout<<"out:"<<endl; 90     DeQueue(p);//輸出A
91     DeQueue(p);//輸出B
92     DeQueue(p);//輸出C
93     DeQueue(p);//輸出D
94     DeQueue(p);//沒有元素了,出隊失敗
95 }

運行結果:

 

補充:比較循環隊列和鏈隊列

時間上:

基本操作入隊和出隊等都是常數時間,O(1)。

同樣是O(1),也有細微差異。循環隊列事先申請好空間,使用期間不釋放。鏈隊列每次申請和釋放節點會存在時間開銷。

空間上:

循環隊列必須有一個固定的長度,如果存儲的元素個數比長度小很多,造成空間浪費的問題。

鏈隊列只需要一個指針域,代碼中有講到占8個字節(rear和front兩個指針變量分別占4個字節)。所以空間上鏈隊列更靈活。

總結:

在可以確定隊列長度最大值的情況下,建議用循環隊列。如果無法預估隊列長度,用鏈隊列。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM