喜歡的話可以掃碼關注我們的公眾號哦,更多精彩盡在微信公眾號【程序猿聲】

01 循環鏈表
1.1 什么是循環鏈表?
前面介紹了單鏈表,相信大家還記得相關的概念。其實循環鏈表跟單鏈表也沒有差別很多,只是在某些細節上的處理方式會稍稍不同。
在此之前,大家可以先思考一個問題:單鏈表中,要找到其中某個節點只需要從頭節點開始遍歷鏈表即可,但是有些時候我們的想法是,能不能從任意節點開始遍歷,也能找到我們需要的那個節點呢?
其實啊,這個實現也很簡單自然,把整個鏈表串成一個環問題就迎刃而解了。所以,關於循環鏈表,我們有了如下的定義:
將單鏈表中的尾節點的指針域由NULL改為指向頭結點,使整個單鏈表形成一個環,這種頭尾相接的單鏈表就可以稱之為**單循環鏈表,簡稱循環鏈表(circular linked list)。
1.2 循環鏈表圖示
這里我們討論的鏈表還是設置一個頭結點(當然,鏈表並不是一定需要一個頭結點)。
- 當鏈表為空的時候,我們可以有如下表示:

- 對於非空鏈表則可以這樣表示:

-
不得不提的一點
對於循環鏈表這種設計,我們在遍歷的時候的結束條件就不再是p為空的時候結束了。而是p等於頭結點的時候遍歷才完成。這一點希望大家切記。
-
再多說兩句
我們知道,有了頭結點,我們可以輕而易舉訪問第一個節點。但是當要訪問最后一個節點時,卻要將整個鏈表掃一遍,效率不可謂不低下……那么,有沒有更好的辦法呢?
當然是有的,只不過這里我們需要將循環鏈表再做一個小小的改進,具體表現為:
從上圖可以看出,我們拋棄了以往的頭指針,取而代之的是采用一個尾指針指向了最后一個節點。而且,因為鏈表是循環的,當我們需要訪問第一個節點時,也very easy!只需要尾指針往后走一個就到前面了。
1.3 循環鏈表代碼
關於循環鏈表的插入刪除等操作,其實是和單鏈表一樣的。唯一不同的就是遍歷的時候的結束條件不同而已。因此咱們在這里就不做過多篇幅的介紹,直接貼完整代碼吧。
循環鏈表就是末尾指向頭形成一個循環的鏈表.實現思路也很簡單,大體把單鏈表代碼做個小小的改動就OK了.這次還是封裝在一個類里面吧.
CircleLinkList.h 類頭文件,各種聲明定義
1#pragma once //VC防止頭文件重復包含的一條預編譯指令
2
3#define status bool
4#define OK true
5#define ERROR false
6#define YES true
7#define NO false
8
9template <typename DType>
10class Node
11{
12public:
13 DType data;
14 Node * pnext;
15};
16template <typename DType>
17class CCircleLinkList
18{
19private:
20 Node<DType> *phead;
21public:
22 CCircleLinkList();
23 ~CCircleLinkList();
24public:
25 //初始化鏈表
26 status InitCList();
27 //獲取鏈表長度
28 int GetCListLength();
29 //增加一個節點 前插法
30 status AddCListNodeFront(DType idata);
31 //增加一個節點 后插法
32 status AddCListNodeBack(DType idata);
33 //判斷鏈表是否為空
34 status IsCListEmpty();
35 //獲取指定位置節點值(注意,本程序規定0號為頭節點,e獲取刪除元素)
36 status GetCListIndexNode(DType *e, int index);
37 //刪除指定位置節點(e獲取刪除元素)
38 status DeleteCListIndexNode(DType *e, int index);
39 //查找鏈表中指定值(pindex獲取位置0==>not found)
40 status SearchCListNode(DType SData, int *pindex);
41 //指定位置前插
42 status InsertCListNodeFront(DType IData, int index);
43 //指定位置后插
44 status InsertCListNodeBack(DType IData, int index);
45 //清空鏈表(保留根節點)
46 status ClearCList();
47 //銷毀鏈表(all delete)
48 status DestoryCList();
49 //尾部刪除一個元素
50 status DeleteCListNodeBack();
51 //打印鏈表 此函數根據實際情況而定
52 void PrintCList();
53};
CircleLinkList.cpp 類的具體實現代碼
1#include "CircleLinkList.h"
2
3#include <stdio.h>
4
5template <typename DType>
6CCircleLinkList<DType>::CCircleLinkList()
7{
8 cout << "鏈表創建" << endl;
9 InitCList();
10}
11
12template <typename DType>
13CCircleLinkList<DType>::~CCircleLinkList()
14{
15 cout << "鏈表銷毀" << endl;
16 DestoryCList();
17}
18
19
20//初始化鏈表
21template <typename DType>
22status CCircleLinkList<DType>::InitCList()
23{
24 Node<DType> * ph = new Node<DType>;
25 if (ph != NULL)
26 {
27 ph->pnext = ph; //尾指向頭
28 this->phead = ph; //頭結點
29 return OK;
30 }
31
32 return ERROR;
33
34}
35
36//獲取鏈表長度(head_node is not included)
37template <typename DType>
38int CCircleLinkList<DType>::GetCListLength()
39{
40 int length = 0;
41 Node<DType> * ph = this->phead;
42 while (ph->pnext != this->phead)
43 {
44 length++;
45 ph = ph->pnext;
46 }
47 return length;
48}
49
50//增加一個節點 前插法
51template <typename DType>
52status CCircleLinkList<DType>::AddCListNodeFront(DType idata)
53{
54 Node<DType> * pnode = new Node<DType>;
55 if (pnode != NULL)
56 {
57 pnode->data = idata;
58 pnode->pnext = this->phead->pnext;
59 this->phead->pnext = pnode; //掛載
60
61 return OK;
62 }
63
64 return ERROR;
65
66}
67
68
69//增加一個節點 尾插法
70template <typename DType>
71status CCircleLinkList<DType>::AddCListNodeBack(DType idata)
72{
73 Node<DType> * pnode = new Node<DType>;
74 Node<DType> * ph = this->phead;
75 if (pnode != NULL)
76 {
77 while (ph->pnext != this->phead)
78 {
79 ph = ph->pnext;
80 }
81 pnode->data = idata;
82 pnode->pnext = this->phead;
83 ph->pnext = pnode; //掛載
84 return OK;
85 }
86
87 return ERROR;
88}
89//判斷鏈表是否為空
90template <typename DType>
91status CCircleLinkList<DType>::IsCListEmpty()
92{
93 if (this->phead->pnext == this->phead)
94 {
95 return YES;
96 }
97
98 return NO;
99}
100//獲取指定位置節點值(注意,本程序規定0號為頭節點,e獲取節點的值)
101template <typename DType>
102status CCircleLinkList<DType>::GetCListIndexNode(DType *e, int index)
103{
104 Node<DType> * ph = this->phead;
105 int i = 0; //計數器
106 if (ph->pnext == this->phead || index < 1 || index > GetCListLength())
107 {
108 return ERROR; //異常 處理
109 }
110 while (ph->pnext != this->phead)
111 {
112 i++;
113 ph = ph->pnext;
114 if (i == index)
115 {
116 *e = ph->data;
117 return OK;
118 }
119 }
120
121 return ERROR;
122}
123//刪除指定位置節點(e獲取刪除元素)
124template <typename DType>
125status CCircleLinkList<DType>::DeleteCListIndexNode(DType *e, int index)
126{
127 Node<DType> * ph = this->phead;
128 int i = 0; //計數器
129 Node<DType> * q = nullptr;
130 if (ph->pnext == this->phead || index < 1 || index > GetCListLength())
131 {
132 return ERROR; //異常 處理
133 }
134 while (ph->pnext != this->phead)
135 {
136 i++;
137 q = ph; //保存備用
138 ph = ph->pnext;
139 if (i == index)
140 {
141 *e = ph->data;
142 q->pnext = ph->pnext; //刪除出局
143 delete ph;
144 return OK;
145 }
146 }
147 return ERROR;
148}
149//查找鏈表中指定值(pindex獲取位置 0==>not found)
150template <typename DType>
151status CCircleLinkList<DType>::SearchCListNode(DType SData, int *pindex)
152{
153 Node<DType> * ph = this->phead;
154 int iCount = 0;//計數器
155 while (ph->pnext != this->phead)
156 {
157 iCount++;
158 ph = ph->pnext;
159 if (ph->data == SData)
160 {
161 *pindex = iCount;
162 return YES;
163 }
164 }
165 *pindex = 0;
166 return NO;
167
168}
169//指定位置前插
170template <typename DType>
171status CCircleLinkList<DType>::InsertCListNodeFront(DType IData, int index)
172{
173 Node<DType> * ph = this->phead;
174 if (ph->pnext == this->phead || index < 1 || index > GetCListLength())
175 {
176 return ERROR; //異常 處理
177 }
178 int iCount = 0; //計數器
179 Node<DType> * q = nullptr; //備用
180 while (ph->pnext != this->phead)
181 {
182 iCount++;
183 q = ph;
184 ph = ph->pnext;
185 if (iCount == index)
186 {
187 Node<DType> * p = new Node<DType>;
188 p->data = IData;
189 p->pnext = ph;
190 q->pnext = p; //前插
191 return OK;
192 }
193 }
194 return ERROR;
195}
196//指定位置后插
197template <typename DType>
198status CCircleLinkList<DType>::InsertCListNodeBack(DType IData, int index)
199{
200 Node<DType> * ph = this->phead;
201 if (ph->pnext == this->phead || index < 1 || index > GetCListLength())
202 {
203 return ERROR; //異常 處理
204 }
205 int iCount = 0; //計數器
206 Node<DType> * q = nullptr; //備用
207 while (ph->pnext != this->phead)
208 {
209 iCount++;
210 q = ph;
211 ph = ph->pnext;
212 if (iCount == index)
213 {
214 Node<DType> * p = new Node<DType>;
215 q = ph;
216 ph = ph->pnext; //后插就是后一個節點的前插咯
217 p->data = IData;
218 p->pnext = ph;
219 q->pnext = p;
220 return OK;
221 }
222 }
223 return ERROR;
224}
225//清空鏈表(保留根節點)
226template <typename DType>
227status CCircleLinkList<DType>::ClearCList()
228{
229 Node<DType> * ph = this->phead;
230 Node<DType> * q = nullptr; //防止那啥,野指針
231 ph = ph->pnext;//保留頭節點
232 while (ph != this->phead)
233 {
234 q = ph;
235 ph = ph->pnext;
236 delete q; //釋放
237 }
238 this->phead->pnext = this->phead;
239 return OK;
240}
241//銷毀鏈表(all delete)
242template <typename DType>
243status CCircleLinkList<DType>::DestoryCList()
244{
245 ClearCList();
246 delete this->phead;//釋放頭結點
247 return OK;
248}
249
250template <typename DType>
251status CCircleLinkList<DType>::DeleteCListNodeBack()
252{
253 Node<DType> * ph = this->phead;
254 Node<DType> * q = nullptr; //備用
255 if (ph->pnext == this->phead)
256 {
257 return ERROR; //鏈表都空了還刪雞毛
258 }
259 while (ph->pnext != this->phead)
260 {
261 q = ph;
262 ph = ph->pnext;
263 }
264 q->pnext = this->phead;
265 delete ph;
266
267 return OK;
268
269}
270template <typename DType>
271void CCircleLinkList<DType>::PrintCList()
272{
273 Node<DType> * ph = this->phead;
274 if (ph->pnext == this->phead)
275 {
276 cout << "鏈表為空,打印雞毛" << endl;
277 return;
278 }
279 int i = 1;
280 cout << "===============print list================" << endl;
281 while (ph->pnext != this->phead)
282 {
283 ph = ph->pnext;
284 cout << "node[" << i++ << "] = " << ph->data << endl;
285 }
286}
CircleLinkListTest.cpp 測試代碼
1#include <iostream>
2#include "CircleLinkList.h"
3#include "CircleLinkList.cpp"
4
5using namespace std;
6
7int main()
8{
9 CCircleLinkList<int> * pMySList = new CCircleLinkList<int>;
10 cout << pMySList->IsCListEmpty() << endl;
11 pMySList->AddCListNodeFront(111);
12 pMySList->AddCListNodeFront(222);
13 pMySList->AddCListNodeFront(333);
14
15 cout << "鏈表長度" << pMySList->GetCListLength() << endl;
16
17 pMySList->PrintCList();
18
19 pMySList->AddCListNodeBack(444);
20 pMySList->AddCListNodeBack(555);
21 pMySList->AddCListNodeBack(666);
22
23 pMySList->PrintCList();
24
25 cout << pMySList->IsCListEmpty() << endl;
26 cout << "鏈表長度" << pMySList->GetCListLength() << endl;
27
28 int GetElem, GetIndex;
29 pMySList->GetCListIndexNode(&GetElem, 2);
30 cout << "Got Elem = " << GetElem << endl;
31
32 pMySList->GetCListIndexNode(&GetElem, 6);
33 cout << "Got Elem = " << GetElem << endl;
34
35 pMySList->GetCListIndexNode(&GetElem, 4);
36 cout << "Got Elem = " << GetElem << endl;
37
38 pMySList->DeleteCListIndexNode(&GetElem, 1);
39 cout << "del Elem = " << GetElem << endl;
40 pMySList->DeleteCListIndexNode(&GetElem, 3);
41 cout << "del Elem = " << GetElem << endl;
42
43
44 pMySList->PrintCList();
45
46 pMySList->SearchCListNode(555, &GetIndex);
47 cout << "Search Index = " << GetIndex << endl;
48 pMySList->SearchCListNode(111, &GetIndex);
49 cout << "Search Index = " << GetIndex << endl;
50 pMySList->SearchCListNode(666, &GetIndex);
51 cout << "Search Index = " << GetIndex << endl;
52
53 pMySList->InsertCListNodeFront(333, 1);
54 pMySList->InsertCListNodeFront(444, 4);
55
56 pMySList->PrintCList();
57
58 pMySList->InsertCListNodeBack(777, 3);
59 pMySList->InsertCListNodeBack(888, 5);
60
61 pMySList->PrintCList();
62
63 pMySList->DeleteCListNodeBack();
64 pMySList->PrintCList();
65 pMySList->DeleteCListNodeBack();
66 pMySList->PrintCList();
67 pMySList->DeleteCListNodeBack();
68 pMySList->PrintCList();
69 return 0;
70}
代碼未經嚴謹測試,如果有誤,歡迎指正.
02 雙向鏈表(doubly linked list)
2.1 雙向鏈表又是個什么表?
-
引言
我們都知道,在單鏈表中由於有next指針的存在,需要查找下一個節點的時間復雜度也只是O(1)而已。但是正如生活並不總是那么如意,當我們想再吃一吃回頭草的時候,查找上一節點的時間復雜度卻變成了O(n),這真是讓人頭大。
-
雙向鏈表
不過我們老一輩的科學家早就想到了這個問題,並提出了很好的解決方法。在每個節點中再多加了一個指針域,從而讓一個指針域指向前一個節點,一個指針域指向后一個節點。也正是如此,就有了我們今天所說的雙向鏈表了。
雙向鏈表(doubly linked list)是在單鏈表的每個節點中,再設置一個指向其前驅節點的指針域。
2.2 雙向鏈表圖示
國際慣例,這里我們依舊引入了頭結點這個玩意兒。不僅如此,為了增加更多的刺激,我們還引入了一個尾節點。
-
雙向鏈表為空時
雙向鏈表在初始化時,要給首尾兩個節點分配內存空間。成功分配后,要將首節點的prior指針和尾節點的next指針指向NULL,這是之后用來判斷空表的條件。同時,當鏈表為空時,要將首節點的next指向尾節點,尾節點的prior指向首節點。
-
雙向鏈表不為空時
每一個節點(首位節點除外)的兩個指針域都分別指向其前驅和后繼。
2.3 雙向鏈表存儲結構代碼描述
雙向鏈表一般可以采用如下的存儲結構:
1/*線性表的雙向鏈表存儲結構*/
2typedef struct DulNode
3{
4 DataType data;
5 struct DulNode *prior; //指向前驅的指針域
6 struct DulNode *next; //指向后繼的指針域
7}DulNode, *DuLinkList;
2.4 雙向鏈表的插入
其實大家別看多了一個指針域就怕了。這玩意兒還是跟單鏈表一樣簡單得一批,需要注意的就是理清各個指針之間的關系。該指向哪的就讓它指向哪里去就OK了。具體的表現為:

代碼描述也很簡單:
1s->prior=p;
2s->next=p->next;
3p->next->prior=s;
4p->next=s;
2.5 雙向鏈表的刪除
刪除操作和單鏈表的操作基本差不多的。都是坐下常規操作了。只是多了一個前驅指針,注意一下即可。如下圖:

代碼描述:
1 p->prior->next=p->next;
2 p->next->prior=p->prior;
3 free(p);
2.6 雙向鏈表的完整代碼
其他操作基本和單鏈表差不多了。在這里就不再做過多贅述,大家直接看完整代碼即可。
1#include<stdio.h>
2#include<stdlib.h>
3#include<time.h>
4#define OK 1
5#define ERROR 0
6#define TRUE 1
7#define FALSE 0
8typedef int status;
9typedef int elemtype;
10typedef struct node{
11 elemtype data;
12 struct node * next;
13 struct node * prior;
14}node;
15typedef struct node* dlinklist;
16
17status visit(elemtype c){
18 printf("%d ",c);
19}
20
21/*雙向鏈表初始化*/
22status initdlinklist(dlinklist * head,dlinklist * tail){
23 (*head)=(dlinklist)malloc(sizeof(node));
24 (*tail)=(dlinklist)malloc(sizeof(node));
25 if(!(*head)||!(*tail))
26 return ERROR;
27 /*這一步很關鍵*/
28 (*head)->prior=NULL;
29 (*tail)->next=NULL;
30 /*鏈表為空時讓頭指向尾*/
31 (*head)->next=(*tail);
32 (*tail)->prior=(*head);
33}
34
35/*判定是否為空*/
36status emptylinklist(dlinklist head,dlinklist tail){
37 if(head->next==tail)
38 return TRUE;
39 else
40 return FALSE;
41}
42
43/*尾插法創建鏈表*/
44status createdlinklisttail(dlinklist head,dlinklist tail,elemtype data){
45 dlinklist pmove=tail,pinsert;
46 pinsert=(dlinklist)malloc(sizeof(node));
47 if(!pinsert)
48 return ERROR;
49 pinsert->data=data;
50 pinsert->next=NULL;
51 pinsert->prior=NULL;
52 tail->prior->next=pinsert;
53 pinsert->prior=tail->prior;
54 pinsert->next=tail;
55 tail->prior=pinsert;
56}
57
58/*頭插法創建鏈表*/
59status createdlinklisthead(dlinklist head,dlinklist tail,elemtype data){
60 dlinklist pmove=head,qmove=tail,pinsert;
61 pinsert=(dlinklist)malloc(sizeof(node));
62 if(!pinsert)
63 return ERROR;
64 else{
65 pinsert->data=data;
66 pinsert->prior=pmove;
67 pinsert->next=pmove->next;
68 pmove->next->prior=pinsert;
69 pmove->next=pinsert;
70 }
71}
72
73/*打印鏈表*/
74status printplist(dlinklist head,dlinklist tail){
75 /*dlinklist pmove=head->next;
76 while(pmove!=tail){
77 printf("%d ",pmove->data);
78 pmove=pmove->next;
79 }
80 printf("\n");
81 return OK;*/
82 dlinklist pmove=head->next;
83 while(pmove!=tail){
84 visit(pmove->data);
85 pmove=pmove->next;
86 }
87 printf("\n");
88}
89
90/*返回第一個值為data的元素的位序*/
91status locateelem(dlinklist head,dlinklist tail,elemtype data){
92 dlinklist pmove=head->next;
93 int pos=1;
94 while(pmove&&pmove->data!=data){
95 pmove=pmove->next;
96 pos++;
97 }
98 return pos;
99}
100
101/*返回表長*/
102status listlength(dlinklist head,dlinklist tail){
103 dlinklist pmove=head->next;
104 int length=0;
105 while(pmove!=tail){
106 pmove=pmove->next;
107 length++;
108 }
109 return length;
110}
111
112
113/*刪除鏈表中第pos個位置的元素,並用data返回*/
114status deleteelem(dlinklist head,dlinklist tail,int pos,elemtype *data){
115 int i=1;
116 dlinklist pmove=head->next;
117 while(pmove&&i<pos){
118 pmove=pmove->next;
119 i++;
120 }
121 if(!pmove||i>pos){
122 printf("輸入數據非法\n");
123 return ERROR;
124 }
125 else{
126 *data=pmove->data;
127 pmove->next->prior=pmove->prior;
128 pmove->prior->next=pmove->next;
129 free(pmove);
130 }
131}
132
133/*在鏈表尾插入元素*/
134status inserttail(dlinklist head,dlinklist tail,elemtype data){
135 dlinklist pinsert;
136 pinsert=(dlinklist)malloc(sizeof(node));
137 pinsert->data=data;
138 pinsert->next=NULL;
139 pinsert->prior=NULL;
140 tail->prior->next=pinsert;
141 pinsert->prior=tail->prior;
142 pinsert->next=tail;
143 tail->prior=pinsert;
144 return OK;
145}
146int main(void){
147 dlinklist head,tail;
148 int i=0;
149 elemtype data=0;
150 initdlinklist(&head,&tail);
151 if(emptylinklist(head,tail))
152 printf("鏈表為空\n");
153 else
154 printf("鏈表不為空\n");
155 printf("頭插法創建鏈表\n");
156 for(i=0;i<10;i++){
157 createdlinklisthead(head,tail,i);
158 }
159 traverselist(head,tail);
160
161 for(i=0;i<10;i++){
162 printf("表中值為%d的元素的位置為",i);
163 printf("%d位\n",locateelem(head,tail,i));
164 }
165 printf("表長為%d\n",listlength(head,tail));
166 printf("逆序打印鏈表");
167 inverse(head,tail);
168 for(i=0;i<10;i++){
169 deleteelem(head,tail,1,&data);
170 printf("被刪除的元素為%d\n",data);
171 }
172 traverselist(head,tail);
173 if(emptylinklist(head,tail))
174 printf("鏈表為空\n");
175 else
176 printf("鏈表不為空\n");
177 printf("尾插法創建鏈表\n");
178 for(i=0;i<10;i++){
179 //inserttail(head,tail,i);
180 createdlinklisttail(head,tail,i);
181 }
182 printplist(head,tail);
183
184}
PS:學有余力的小伙伴們,可以嘗試一下結合這節課介紹的內容,做一個循環雙向鏈表出來哦。
完整結語
關於線性表大體內容三節課基本講完了。不過還有很多好玩的操作,比如鏈表的逆序,合並,排序等等。這些內容咱們下次有空再聊吧。祝大家學有所成!