最近學習了數據結構中的鏈表。
關於鏈表,個人整理筆記如下:
什么是鏈表?
鏈表是物理存儲單元上非連續、非順序的存儲結構。與我們之前學習過的數組同為存儲結構,區別是數組是連續的、順序的存儲結構。
在鏈表這種非連續、非順序的存儲結構中,每個元素以結點的形式存儲。而每個結點都由數據域和指針域構成,如下圖所示。
關於頭結點:
鏈表可以有頭結點也可以沒有,區別在於鏈表有頭結點雖浪費空間,但易理解,邊界好處理,不易出錯,代碼簡單,相反無頭結頭節省空間,難理解,邊界不易處理,代碼稍復雜。
有頭結點的引入是為了對鏈表刪除、逆向、建立的時候操作更統一,不用專門對第一個元素或最后一個元素進行單獨處理。
可能講述得不是那么清楚,更多詳情請參閱此博文 http://blog.csdn.net/21aspnet/article/details/160019
為什么要使用鏈表呢?
首先,數組是物理內存上連續的、順序的空間,那么要是數組大小太大或沒有連續大小的空間時就尬尷了,此時就需要用到鏈表了。
其次,我們如果我們對一組數據進行刪除操作時,用數組的話,我們就需要將要刪除的那個數據所在的位置后邊的所有數據都得向前移動一個位置,而當我們添加數據時,同樣也是這樣的操作。
那么使用鏈表的話,就是把那個結點的指針域所指向地址指向下一個結點的地址就 ok 了。而添加數據呢,也是類似的指針操作。
當然數組仍然存在肯定是還有鏈表不能替代的地方,使用鏈表的時候呢,我們要查找某個數據時就必需得從第一個結點開始依次的查找,
還有當我們要打印出鏈表中最后一個數據時,用數組直接使用下標地址就可以打印出來了,然而鏈表還在一個接一個地跟着指針往下傳,
同樣的我們用數組打印某個指定序列內的數據時直接用標法可以解決,而鏈表就比較麻煩了。
那么綜上所述,可以知道為什么要用到鏈表,數組和鏈表存在的意義了。
那什么又是單向鏈表?什么又是循環鏈表?就用一個結構圖來表明它們的關系吧。
線性表:一種最簡單、常用的數據結構,數據元素之間是一對一的關系。
那么,怎樣建立一個單鏈表呢?
首先定義單鏈表的存儲結構
// 為 int 類型創建 ElemType 別名
typedef int ElemType; // 定義鏈表儲結構
typedef struct LNode { ElemType data; struct LNode *next; }LNode, *LinkList;
此處定義單鏈表每個結點的存儲結構,包括 存儲結點的數據域 data 和 存儲后繼結點位置的指針域 next 。
為了方便閱讀使用別名 ElemType 表示 int 型,同時也為了提高程序可讀性,對同一結構體指針類型命了兩個名 LinkList 、LNode * ,兩者本質上是等價的。
通常習慣用 LinkList 定義單鏈表,表示某單鏈表的頭指針;用LNode *定義指向單鏈表中任意結點的指針變量。
如: 定義LinkList L ,則L為單鏈表的頭指針; 定義LNode *p ,則p為指向單鏈表中某個結點的指針,用 *p 表示該結點。
單鏈表由表頭指針唯一確定,因此單鏈表可以用頭指針的名字來命名,如頭指針名為 L ,則簡稱該鏈表為為表 L。
注意區分指針變量和結點變量兩個不同概念,若定義 LinkList p 或 LNode *p ,則p為指向某結點的指針變量,表示該結點的地址;而*p為對應結點的變量,表示該結點的名稱。
此處為了 便於首元結點的處理 便於空表和非空表的統一處理 為鏈表增加頭結點。
初始化單鏈表
1.生成一個新結點作為頭結點 ,用頭指針 L 指向頭結點。
2.頭結點的指針域置空。
此處: 可以使用關鍵字 new 為結點分配內存,也可以用頭文件 stdlib.h 中的 malloc 分配內存。
void InitList(LinkList &L) { L = new LNode; // 分配內存 //L = (struct LNode *)malloc(sizeof(struct LNode)); // 調用頭文件 stdlib.h 中的 malloc 分配內存
L->next = NULL; }
創建單鏈表
初始化操作是創建一個只有頭結點的空鏈表,那么如何創建包括若干結點的鏈表呢?
創建鏈表時根據結點插入位置不同,鏈表的創建方法可分為前插法和后插法。
- 前插法
前插法通過將新結點逐個插入鏈表的頭部(頭結點之后)來創建鏈表,每次申請一個新結點,讀入相應的數據元素值,然后將新結點插入到頭結點之后 。
1.創建一個只有頭結點的空鏈表。
2.根據創建鏈表結點元素個數 n ,循環n次執行以下操作:
(1)生成一個新結點 *p ;
(2)將輸入元素賦新結點 *p 的數據域中;
(3)將新結點 *p 插入到頭結點之后。
// 前插法插入
void CreateList_H(LinkList &L, int n) { LNode *p;
for(int i = 0; i < n; i++) { p = new LNode; cin>>p->data; p->next = L->next; L->next = p; } }
- 后插法
后插法通過將新結點逐個插入到鏈表的尾部來創建鏈表。同前插法,每次申請一個新結點,讀入相應的數據元素值。不同的是,為了使新結點能夠插入到單鏈表尾部,需要增加一個尾指針 tailNode 指向鏈表的尾結點。
1.創建一個只有頭結點的空鏈表。
2.尾指針 tailNode 初始化,指向頭結點。
3.根據創建鏈表結點元素個數 n,循環n次執行以下操作:
(1)生成一個新結點 *p ;
(2)輸入元素值賦給新結點 *p 的數據域;
(3)將新結點 *p 插入到 *tailNode 之后;
(4)尾指針tailNode指向新的尾結點 *p。
// 尾插法插入
void CreateList_R(LinkList &L, int n) { LNode *p, *tailNode; tailNode = L; for(int i = 0; i < n; i++) { p = new LNode; cin>>p->data; p->next = NULL; tailNode->next = p; // 將新結點*p插入尾結點*tailNode后的數據域
tailNode = p; // tailNode 指向新的尾結點 *p
} }
單鏈表的插入
創建了含若干個結點的單鏈表,就可能根據需求對鏈表進行新結點的插入操作。
將數據域為 e 的新結點插入到單鏈表的第 i 個結點位置上,即插入到結點 Ni-1 和 Ni 之間。
1.查找結點 Ni-1,並由指針 p 指向該結點;
2.生成一個新的結點 *newNode ;
3.將新結點 *newNode 數據域置為 e,指針域指向結點 Ni;
4.將結點 *p 的指針域指向新結點 *newNode。
// 單鏈表插入新結點
int InsertLinkList(LinkList &L, int i, ElemType e) { LNode *p = L, *newNode; int j = 0; while(p && j < i-1) // 查找第 i-1 個結點 p指向該結點
{ p = p->next; j++; } if(!p || j > i-1) return -1; // 類似前插法
newNode = new LNode; // 生成新結點 newNode
newNode->data = e; newNode->next = p->next; p->next = newNode; return 1; }
單鏈表的刪除
單鏈表對其中某個結點的刪除也是基本操作,同單鏈表插入元素一樣,將某個結點上從鏈表中刪除,首先查找結點 Ni-1然后刪除該位置結點。
1.查找結點 Ni-1 位置,並由指針 p 指向該結點;
2.臨時保存待刪除結點 Ni 地址在 tempNode 中,以備釋放結點空間;
3.將結點 *p 指針域指向結點 Ni 的的后繼結點;
4.釋放結點 Ni 的空間。
// 刪除單鏈表元素
int LinkListDelete(LinkList &L, int i) { int j = 0; LNode *p, *tempNode; p = L; while((p->next) && (j < i-1)) // 查找第 i-1 個元素 p指向該結點
{ p = p->next; j++; } if(!(p->next) || j > i-1) // 判斷插入位置是否合理
return -1; tempNode = p->next; // 臨時保存被刪除結點的地址以備釋放
p->next = tempNode->next; // 改變刪除結點前驅結點的指針域
delete tempNode; // 釋放刪除結點空間
return 1; }
單鏈表打印輸出
如果要查看單鏈表中元素,怎么做呢?
1.生成新結點 *p,其指單鏈表首元結點;
2從首元結點開始依次順着鏈表指針域 next 向下訪問,只要當前結點的指針 p 不為空(NULL)則執行以下操作:
(1)打印輸出當前結點數據域值;
(2)p 指向下一個結點;
// 打印輸出單鏈表元素
void PrintLinkList(LinkList &L) { LNode *p; p = L->next; while(p != NULL) { cout<<p->data<<" "; p = p->next; } cout<<endl; }
單鏈表的取值
和順序表不同,鏈表中邏輯相鄰的結點並沒有存儲在物理相鄰的單元中,這樣根據給定結點位置序號 i,在鏈表中獲取該結點數據域的值時不能像順序表那樣隨機訪問,只能從鏈表的首元結點出發,順着鏈表指針域next 逐個結點向下訪問。
1.用指針 p 指向首元結點, 用 j 做計數器初值賦為 1;
2.從首元結點開始依次順着鏈表指針域 next 向下訪問,只要指向當前結點的指針 p 不為空(NULL) ,並且沒有到達序號為 i 的結點,則循環執行以下操作:
(1)p 指向下一個結點 ;
(2)計數器 j 相應加 1;
3.退出循環時,如果指針 p 為空,或者計數器 j 大於 i,則說明指定的序號 i 值不合法(i 大於表長 n 或 i 小於等於 0),取值失敗提示信息 返回 -1 ;否則取值成功,此時 j = i 時,p 所指的結點就是要找的第 i 個結點,可以輸出此結點數據域中元素值,返回 1。
// 單鏈表取值
int getElem(LinkList &L, int i) { LNode *p; int j = 1; // 計數器 j 初值賦為 1
p = L->next; // 初始化 p 指向首元結點
while(p && j < i) // 順鏈表指針域向后掃描 直到 p 為空或指向第 i 個元素
{ p = p->next; j++; } if(!p || j > i) // i 值不合法 i > n 或 i <= 0
{ cout<<"No the Node"<<endl; return -1; } cout<<p->data; return 1; }
單鏈表的查找
鏈表的查找過程和順序表類似,從鏈表的首元結點出發,依次將結點數據域中的值與給定值 e 進行比較,返回查找結果。
1.用指針 p 指向首元結點 ;
2.從首元結點開始依次順着鏈表指針域向下查找,只要指向當前結點的指針 p 不為空,並且 p 所指結點的數據域與給定值 e 不等時,則執行以下操作:
(1) p 指向下一個結點。
3.判斷 p 是否為空(NULL),如為空,則鏈表中沒有結點數據域值為所給定的 e ,給出提示信息;如不為空,則打印輸出其所在位置。
// 單鏈表的查找
void LocationElem(LinkList L, ElemType e) { LNode *p; int j = 1; p = L->next; // 初始化 p 指向首元結點
while(p && p->data != e) // 順着鏈表指針域向下掃描 直到 p 為空或所指結點的數據域等於 e
{ j++; p = p->next; } if(p != NULL) // 判斷是否查找到結點數據域為 e 的結點
cout<<"The element of "<<e<<" local "<<j<<endl; else cout<<"No the element"<<endl; }
最后,完整代碼如下 :
1 #include <iostream>
2
3 using namespace std; 4
5 // 為 int 類型創建 ElemType 別名
6 typedef int ElemType; 7
8 // 定義鏈表儲結構
9 typedef struct LNode 10 { 11 ElemType data; 12 struct LNode *next; 13 }LNode, *LinkList; 14
15
16 // 初始化鏈表
17 void InitList(LinkList &L) 18 { 19 L = new LNode; // 分配內存 20 //L = (struct LNode *)malloc(sizeof(struct LNode)); // 調用頭文件 stdlib.h 中的 malloc 分配內存
21 L->next = NULL; 22 } 23
24 // 單鏈表插入新結點
25 int InsertLinkList(LinkList &L, int i, ElemType e) 26 { 27 LNode *p = L, *newNode; 28 int j = 0; 29 while(p && j < i-1) // 查找第 i-1 個結點 p指向該結點
30 { 31 p = p->next; 32 j++; 33 } 34 if(!p || j > i-1) 35 return -1; 36 // 類似前插法
37 newNode = new LNode; // 生成新結點 newNode
38 newNode->data = e; 39 newNode->next = p->next; 40 p->next = newNode; 41 return 1; 42 } 43
44
45 // 打印輸出鏈表
46 void PrintList(LinkList &L) 47 { 48 LNode* p; 49 p = L->next; 50 while(p != NULL) 51 { 52 cout<<p->data<<" "; 53 p = p->next; 54 } 55 cout<<endl; 56 } 57
58 // 單鏈表取值
59 int getElem(LinkList &L, int i) 60 { 61 LNode *p; 62 int j = 1; // 計數器 j 初值賦為 1
63 p = L->next; // 初始化 p 指向首元結點
64 while(p && j < i) // 順鏈表指針域向后掃描 直到 p 為空或指向第 i 個元素
65 { 66 p = p->next; 67 j++; 68 } 69 if(!p || j > i) // i 值不合理 i > n 或 i <= 0
70 { 71 cout<<"所給定 i 值不合理"<<endl; 72 return -1; 73 } 74 cout<<p->data; 75 return 1; 76 } 77
78 // 單鏈表的查找
79 void LocationElem(LinkList L, ElemType e) 80 { 81 LNode *p; 82 int j = 1; 83 p = L->next; // 初始化 p 指向首元結點
84 while(p && p->data != e) // 順着鏈表指針域向下掃描 直到 p 為空或所指結點的數據域等於 e
85 { 86 j++; 87 p = p->next; 88 } 89 if(p != NULL) // 判斷是否查找到結點數據域為 e 的結點
90 cout<<"鏈表中與 "<<e<<" 等值的數據元素所在結點位於 "<<j<<endl; 91 else
92 cout<<"鏈表中沒有數據元素和 "<<e<<" 等值 "<<endl; 93 } 94
95 // 刪除單鏈表元素
96 int LinkListDelete(LinkList &L, int i) 97 { 98 int j = 0; 99 LNode *p, *tempNode; 100 p = L; 101 while((p->next) && (j < i-1)) // 查找第 i-1 個元素 p指向該結點
102 { 103 p = p->next; 104 j++; 105 } 106 if(!(p->next) || j > i-1) // 判斷插入位置是否合理
107 return -1; 108 tempNode = p->next; // 臨時保存被刪除結點的地址以備釋放
109 p->next = tempNode->next; // 改變刪除結點前驅結點的指針域
110 delete tempNode; // 釋放刪除結點空間
111 return 1; 112 } 113
114 // 前插法插入
115 void CreateList_H(LinkList &L, int n) 116 { 117 LNode *p; 118 // LinkList p;
119 for(int i = 0; i < n; i++) 120 { 121 p = new LNode; 122 cin>>p->data; 123 p->next = L->next; 124 L->next = p; 125 } 126 } 127
128 // 尾插法插入
129 void CreateList_R(LinkList &L, int n) 130 { 131 LNode *p, *tailNode; 132 tailNode = L; 133 for(int i = 0; i < n; i++) 134 { 135 p = new LNode; 136 cin>>p->data; 137 p->next = NULL; 138 tailNode->next = p; // 將新結點*p插入尾結點*tailNode后的數據域
139 tailNode = p; // tailNode 指向新的尾結點 *p
140 } 141 } 142
143 int main() 144 { 145 // 定義鏈表 L
146 LinkList L; 147 // 初始化鏈表
148 InitList(L); 149
150 cout<<"前插法創建有5個元素的單鏈表"<<endl; 151 CreateList_H(L, 5); 152 cout<<"打印鏈表數據"<<endl; 153 PrintList(L); 154
155 cout<<"后插法創建有5個元素的單鏈表"<<endl; 156 CreateList_R(L, 5); 157 cout<<"打印鏈表數據"<<endl; 158 PrintList(L); 159
160 cout<<"在鏈表第三個位置插入數值為 100 的結點"<<endl; 161 InsertLinkList(L, 3, 100); 162 cout<<"打印插入元素后的鏈表數據"<<endl; 163 PrintList(L); 164
165 cout<<"刪除第六個結點"<<endl; 166 LinkListDelete(L, 6); 167 cout<<"打印刪除結點后鏈表數據"<<endl; 168 PrintList(L); 169
170 cout<<"查找元素數據為 100 所在鏈表中位置"<<endl; 171 LocationElem(L, 100); 172
173 }
本文參考:
《數據結構》(C語言版|第2版) 嚴蔚梅 李冬梅 吳偉民 編著