今天看圖的廣度優先遍歷的時候,發現用到了隊列,補一下鏈隊列的知識,參考《大話數據結構》的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個字節)。所以空間上鏈隊列更靈活。
總結:
在可以確定隊列長度最大值的情況下,建議用循環隊列。如果無法預估隊列長度,用鏈隊列。