c語言中的連結串列
線性結構:有且只有一個根節點,且每個節點最多有一個直接前驅和一個直接後繼的非空資料結構
非線性結構:不滿足線性結構的資料結構
連結串列(單向連結串列的建立、刪除、插入、列印)
1、連結串列一般分為:
單向連結串列
雙向連結串列
環形連結串列
2、基本概念
連結串列實際上是線性表的鏈式儲存結構,與陣列不同的是,它是用一組任意的儲存單元來儲存線性表中的資料,儲存單元不一定是連續的,
且連結串列的長度不是固定的,連結串列資料的這一特點使其可以非常的方便地實現節點的插入和刪除操作
連結串列的每個元素稱為一個節點,每個節點都可以儲存在記憶體中的不同的位置,為了表示每個元素與後繼元素的邏輯關係,以便構成“一個節點鏈著一個節點”的鏈式儲存結構,
除了儲存元素本身的資訊外,還要儲存其直接後繼資訊,因此,每個節點都包含兩個部分,第一部分稱為連結串列的資料區域,用於儲存元素本身的資料資訊,這裡用data表示,
它不侷限於一個成員資料,也可是多個成員資料,第二部分是一個結構體指標,稱為連結串列的指標域,用於儲存其直接後繼的節點資訊,這裡用next表示,
next的值實際上就是下一個節點的地址,當前節點為末節點時,next的值設為空指標
1 struct link 2 { 3 int data; 4 struct link *next; 5 };
像上面這種只包含一個指標域、由n個節點連結形成的連結串列,就稱為線型連結串列或者單向連結串列
一旦連結串列中某個節點的指標域資料丟失,那麼意味著將無法找到下一個節點,該節點後面的資料將全部丟失
3、連結串列與陣列比較
陣列(包括結構體陣列)的實質是一種線性表的順序表示方式,它的優點是使用直觀,便於快速、隨機地存取線性表中的任一元素,但缺點是對其進行 插入和刪除操作時需要移動大量的陣列元素,同時由於陣列屬於靜態記憶體分配,定義陣列時必須指定陣列的長度,程式一旦執行,其長度就不能再改變,實際使用個數不能超過陣列元素最大長度的限制,否則就會發生下標越界的錯誤,低於最大長度時又會造成系統資源的浪費,因此空間效率差
4、單向連結串列的建立
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 struct link *AppendNode (struct link *head); 5 void DisplyNode (struct link *head); 6 void DeletMemory (struct link *head); 7 8 struct link 9 { 10 int data; 11 struct link *next; 12 }; 13 14 int main(void) 15 { 16 int i = 0; 17 char c; 18 struct link *head = NULL; //連結串列頭指標 19 printf("Do you want to append a new node(Y/N)?"); 20 scanf_s(" %c", &c); 21 while (c == 'Y' || c == 'y') 22 { 23 head = AppendNode(head);//向head為頭指標的連結串列末尾新增節點 24 DisplyNode(head); //顯示當前連結串列中的各節點的資訊 25 printf("Do your want to append a new node(Y/N)"); 26 scanf_s(" %c", &c); 27 i++; 28 } 29 printf("%d new nodes have been apended", i); 30 DeletMemory(head); //釋放所有動態分配的記憶體 31 32 return 0; 33 } 34 /* 函式功能:新建一個節點並新增到連結串列末尾,返回新增節點後的連結串列的頭指標 */ 35 struct link *AppendNode(struct link *head) 36 { 37 struct link *p = NULL, *pr = head; 38 int data; 39 p = (struct link *)malloc(sizeof(struct link));//讓p指向新建的節點 40 if (p == NULL) //若新建節點申請記憶體失敗,則退出程式 41 { 42 printf("No enough memory to allocate\n"); 43 exit(0); 44 } 45 if (head == NULL) //若原連結串列為空表 46 { 47 head = p; //將新建節點置為頭節點 48 } 49 else //若原連結串列為非空,則將新建節點新增到表尾 50 { 51 while (pr->next != NULL)//若未到表尾,則移動pr直到pr指向表尾 52 { 53 pr = pr->next; //讓pr指向下一個節點 54 } 55 pr->next = p; //讓末節點的指標指向新建的節點 56 } 57 printf("Input node data\n"); 58 scanf_s("%d", &data); //輸入節點資料 59 p->data = data; //將新建節點的資料域賦值為輸入的節點資料值 60 p->next = NULL; //將新建的節點置為表尾 61 return head; //返回新增節點後的連結串列的頭指標 62 } 63 /* 函式的功能:顯示連結串列中所有節點的節點號和該節點中的資料項的內容*/ 64 void DisplyNode (struct link *head) 65 { 66 struct link *p = head; 67 int j = 1; 68 while (p != NULL) //若不是表尾,則迴圈列印節點的數值 69 { 70 printf("%5d%10d\n", j, p->data);//列印第j個節點資料 71 p = p->next; //讓p指向下一個節點 72 j++; 73 } 74 } 75 //函式的功能:釋放head所指向的連結串列中所有節點佔用的記憶體 76 void DeletMemory(struct link *head) 77 { 78 struct link *p = head, *pr = NULL; 79 while (p != NULL) //若不是表尾,則釋放節點佔用的記憶體 80 { 81 pr = p; //在pr中儲存當前節點的指標 82 p = p->next;//讓p指向下一個節點 83 free(pr); //釋放pr指向的當前節點佔用的記憶體 84 } 85 }
上面的程式碼使用了三個函式AppendNode、DisplyNode、DeletMemory
struct link *AppendNode (struct link *head);(函式作用:新建一個節點並新增到連結串列末尾,返回新增節點後的連結串列的頭指標)
void DisplyNode (struct link *head);(函式功能:顯示連結串列中所有節點的節點號和該節點中的資料項的內容)
void DeletMemory (struct link *head);(函式功能:釋放head所指向的連結串列中所有節點佔用的記憶體)
(還使用了malloc函式和free函式)
5、malloc函式
作用:用於分配若干位元組的記憶體空間,返回一個指向該記憶體首地址的指標,若系統不能提供足夠的記憶體單元,函式將返回空指標NULL,函式原型為void *malloc(unsigned int size)
其中size是表示向系統申請空間的大小,函式呼叫成功將返回一個指向void的指標(void*指標是ANSIC新標準中增加的一種指標型別,
具有一般性,通常稱為通用指標或者無型別的指標)常用來說明其基型別未知的指標,即聲明瞭一個指標變數,但未指定它可以指向哪一種基型別的資料,
因此,若要將函式呼叫的返回值賦予某個指標,則應先根據該指標的基型別,用強轉的方法將返回的指標值強轉為所需的型別,然後再進行賦值
1int*pi;2pi = (int*)malloc(4);
其中malloc(4)表示申請一個大小為4位元組的記憶體,將malloc(4)返回值的void*型別強轉為int*型別後再賦值給int型指標變數pi,即用int型指標變數pi指向這段儲存空間的首地址
若不能確定某種型別所佔記憶體的位元組數,則需使用sizeof()計算本系統中該型別所佔的記憶體位元組數,然後再用malloc()向系統申請相應位元組數的儲存空間
pi = (int *)malloc(sizeof(int));
6、free函式
釋放向系統動態申請的由指標p指向的記憶體儲存空間,其原型為:Void free(void *p);該函式無返回值,唯一的形參p給出的地址只能由malloc()和calloc()申請記憶體時返回的地址,
該函式執行後,將以前分配的指標p指向的記憶體返還給系統,以便系統重新分配
為什麼要用free釋放記憶體
(在程式執行期間,用動態記憶體分配函式來申請的記憶體都是從堆上分配的,動態記憶體的生存期有程式設計師自己來決定,使用非常靈活,但也易出現記憶體洩漏的問題,
為了防止記憶體洩漏的發生,程式設計師必須及時呼叫free()釋放已不再使用的記憶體)
7、單向連結串列的刪除操作
刪除操作就是將一個待刪除的節點從連結串列中斷開,不再與連結串列的其他節點有任何聯絡
需考慮四種情況:
1.若原連結串列為空表,則無需刪除節點,直接退出程式
2.若找到的待刪除節點p是頭節點,則將head指向當前節點的下一個節點(p->next),即可刪除當前節點
3.若找到的待刪除節點不是頭節點,則將前一節點的指標域指向當前節點的下一節點(pr->next = p->next),即可刪除當前節點,當待刪除節點是末節點時,
由於p->next值為NULL,因此執行pr->next = p->next後,pr->next的值也變成NULL,從而使pr所指向的節點由倒數第2個節點變成了末節點
4.若已搜尋到表尾(p->next == NULL),仍未找到待刪除節點,則顯示“未找到”,注意:節點被刪除後,只是將它從連結串列中斷開而已,它仍佔用著記憶體,必須釋放其所佔的記憶體,否則將出現記憶體洩漏
(頭結點不是頭指標,注意兩者區別)
8、頭節點和頭指標
頭指標儲存的是頭節點記憶體的首地址,頭結點的資料域可以儲存如連結串列長度等附加資訊,也可以不儲存任何資訊
參考連結---頭指標和頭節點:https://www.cnblogs.com/didi520/p/4165486.html
https://blog.csdn.net/qq_37037492/article/details/78453333
https://www.cnblogs.com/marsggbo/p/6622962.html
https://blog.csdn.net/hunjiancuo5340/article/details/80671298
(圖片出處:https://blog.csdn.net/hunjiancuo5340/article/details/80671298)
值得注意的是:
1.無論連結串列是否為空,頭指標均不為空。頭指標是連結串列的必要元素
2.連結串列可以沒有頭節點,但不能沒有頭指標,頭指標是連結串列的必要元素
3.記得使用free釋放記憶體
單向連結串列的刪除操作實現
1 struct link *DeleteNode (struct link *head, int nodeData) 2 { 3 struct link *p = head, *pr = head; 4 5 if (head == NULL) 6 { 7 printf("Linked table is empty!\n"); 8 return 0; 9 } 10 while (nodeData != p->data && p->next != NULL) 11 { 12 pr = p; /* pr儲存當前節點 */ 13 p = p->next; /* p指向當前節點的下一節點 */ 14 } 15 if (nodeData == p->data) 16 { 17 if (p == head) /* 如果待刪除為頭節點 (注意頭指標和頭結點的區別)*/ 18 { 19 head = p->next; 20 } 21 else /* 如果待刪除不是頭節點 */ 22 { 23 pr->next = p->next; 24 } 25 free(p); /* 釋放已刪除節點的記憶體 */ 26 } 27 else /* 未發現節點值為nodeData的節點 */ 28 { 29 printf("This Node has not been found"); 30 } 31 32 return head; 33 }
9、單向連結串列的插入
向連結串列中插入一個新的節點時,首先由新建一個節點,將其指標域賦值為空指標(p->next = NULL),然後在連結串列中尋找適當的位置執行節點的插入操作,
此時需要考慮以下四種情況:
1.若原連結串列為空,則將新節點p作為頭節點,讓head指向新節點p(head = p)
2.若原連結串列為非空,則按節點值的大小(假設節點值已按升序排序)確定插入新節點的位置,若在頭節點前插入新節點,則將新節點的指標域指向原連結串列的頭節點(p->next = head),且讓head指向新節點(head =p)
3.若在連結串列中間插入新節點,則將新節點的指標域之下一節點(p->next = pr -> next),且讓前一節點的指標域指向新節點(pr->next = p)
4.若在表尾插入新節點,則末節點指標域指向新節點(p->next = p)
單向連結串列的插入操作實現
1 /* 函式功能:向單向連結串列中插入資料 按升序排列*/ 2 struct link *InsertNode(struct link *head, int nodeData) 3 { 4 struct link *p = head, *pr = head, *temp = NULL; 5 6 p = (struct link *)malloc(sizeof(struct link)); 7 if (p == NULL) 8 { 9 printf("No enough meomory!\n"); 10 exit(0); 11 } 12 p->next = NULL; /* 待插入節點指標域賦值為空指標 */ 13 p->data = nodeData; 14 15 if (head == NULL) /* 若原連結串列為空 */ 16 { 17 head = p; /* 插入節點作頭結點 */ 18 } 19 else /* 原連結串列不為空 */ 20 { 21 while (pr->data < nodeData && pr->next != NULL) 22 { 23 temp = pr; /* 儲存當前節點的指標 */ 24 pr = pr->next; /* pr指向當前節點的下一節點 */ 25 } 26 if (pr->data >= nodeData) 27 { 28 if (pr == head) /* 在頭節點前插入新節點 */ 29 { 30 p->next = head; /* 新節點指標域指向原連結串列頭結點 */ 31 head = p; /* 頭指標指向新節點 */ 32 } 33 else 34 { 35 pr = temp; 36 p->next = pr->next; /* 新節點指標域指向下一節點 */ 37 pr->next = p; /* 讓前一節點指標域指向新節點 */ 38 } 39 } 40 else /* 若在表尾插入新節點 */ 41 { 42 pr->next = p; /* 末節點指標域指向新節點*/ 43 } 44 } 45 46 return head; 47 }原文來自:https://www.cnblogs.com/lanhaicode/p/10304567.html