-----想必大多數人和我同樣,剛開始學數據結構中的單鏈表仍是蠻吃力的,特別是後面的雙鏈表操做更是如此。還有就是在實踐代碼操做時,你又會感到無從下手,沒有思路。形成這樣的原因,仍是沒有徹底把鏈表吃透,今天恰好看書又看到了這裏,總結一下,分享給你們,但願對你們有幫助。node
1、鏈表引入的原因:
在一開始,不知你們用了這麼久的數組,你有沒有發現數組存在兩個明顯的缺陷?1)一個是數組中全部元素的類型必須一致;2)第二個是數組的元素個數必須事先制定而且一旦指定以後不能更改。因而乎爲了解決數組的缺陷,先輩們發明的一些特殊方法來解決:a、數組的第一個缺陷靠結構體去解決。結構體容許其中的元素的類型不相同,所以解決了數組的第一個缺陷。因此說結構體是由於數組不能解決某些問題因此才發明的;b、咱們但願數組的大小可以實時擴展。譬如我剛開始定了一個元素個數是10,後來程序運行時以爲不夠所以動態擴展爲20.普通的數組顯然不行,咱們能夠對數組進行封裝以達到這種目的;咱們還可使用一個新的數據結構來解決,這個新的數據結構就是鏈表(幾乎能夠這樣理解:鏈表就是一個元素個數能夠實時變大/變小的數組)。程序員
2、什麼是鏈表?
顧名思義,鏈表就是用鎖鏈鏈接起來的表。這裏的表指的是一個一個的節點(一個節點能夠比喻成大樓裏面的空房子同樣用來存放東西的),節點中有一些內存能夠用來存儲數據(因此叫表,表就是數據表);這裏的鎖鏈指的是連接各個表的方法,C語言中用來鏈接2個表(其實就是2塊內存)的方法就是指針。它的特色是:它是由若干個節點組成的(鏈表的各個節點結構是徹底相似的),節點是由有效數據和指針組成的。有效數據區域用來存儲信息完成任務的,指針區域用於指向鏈表的下一個節點從而構成鏈表。ubuntu
3、單鏈表中的一些細節:
一、單鏈表的構成:
a、鏈表是由節點組成的,節點中包含:有效數據和指針。
b、定義的struct node只是一個結構體,自己並無變量生成,也不佔用內存。結構體定義至關於爲鏈表節點定義了一個模板,可是尚未一個節點,未來在實際建立鏈表時須要一個節點時用這個模板來複制一個便可。例如:api
1 struct node{
2 int data;//有效數據
3
4struct node *pNext;//指向下一個節點的指針
5
6 };//構建一個鏈表的節點
二、堆內存的申請和使用:
a、先了解一下什麼是堆:堆(heap)是種內存管理方式,它的特色是:就是自由管理(隨時申請,靈活,大小塊隨意)。堆內存是操做系統規劃給堆管理器(操做系統中的的一段代碼,屬於操做系統的內存管理單元),來管理的,而後向使用者(用戶進程)提供api(malloc和free)來使用堆內存。
b、爲何要使用堆呢?數組
須要內存容量比較大的時候,須要反覆使用及釋放時,須要反覆使用及釋放不少數據結構(譬如鏈表)的實現都要使用堆內存;它的特色:容量不限(常規使用的需求容量都能知足),申請及釋放都須要手工進行,手工進行的含義就是須要程序員寫代碼明確進行申請malloc及釋放free。若是程序員申請內存並使用沒有釋放,這段內存就丟失了(在堆管理器的記錄中,這段內存仍然屬於你這個進程,可是進程本身又覺得這段內存已經不用了,再用的時候又會申請新的內存塊,這就叫吃內存),稱爲內存泄漏。數據結構
c、基本概念:
做用域:起做用的區域,也就是能夠工做的範圍。
代碼塊:所謂代碼塊,就是用{}括起來的一段代碼。
數據段:數據段存的是數,像全局變量就是存在數據段的
代碼段:存的是程序代碼,通常是隻讀的。
棧(stack):先進後出。C語言中局部變量就分配在棧中。
這裏順便也講一下什麼是棧:
棧是一種數據結構,c語言中使用棧來保存局部變量。棧是被髮明出來管理內存的;它的特色:是先進後出;而先進先出,它是隊列的特色;棧的特色是入口即出口,另一個口是堵死的。因此先進去的必須後出來隊列的特色是入口和出口都有,必須從入口進去,從出口出來,因此先進去的必須先出來,不然就堵住後面的。在c 語言中的局部變量是用棧來實現的。咱們在c中定義一個局部變量時(int a ),編譯器會在棧中分配一段空間(4字節)給這個局部變量用(分配時棧頂指針會移動給出空間,給局部變量a用的意思就是,將這4字節的棧內存地址和咱們定義的局部變量名a 給關聯起來),對應棧的操做時入棧;
-----注意:這裏棧指針的移動和內存分配是自動的(棧本身完成,不用咱們寫代碼去操做);而後等咱們函數退出的時候,局部變量要滅亡。對應棧的操做時出棧。出棧時也是棧頂指針移動將棧空間中與a關聯的那4個字節空間釋放。這個動做也是自動的,也不用人去寫代碼去控制。棧的優勢:棧管理內存,好處是方便,分配和最後回收都不用程序員操心,c語言自動完成。分析一個細節:c語言中,定義局部變量時若是未初始化,則值時隨機的爲何?定義局部變量,其實就是在棧中經過移動棧指針來給程序提供一個內存空間和這個局部變量名綁定,由於這段內存空間在棧上,而棧內存是反覆使用的(髒的,上次用完沒有清零的),因此說使用棧來實現的局部變量定義時若是不顯示初始化,值就是髒的。若是你顯示初始化會怎樣?c語言是經過一個小手段來實現局部變量的初始化的。好比 int a=10;至關於
int a ;
a=10;
棧的缺點:首先,棧是有大小的。因此棧內存大小很差設置,若是過小怕溢出,太大跑浪費內存;因此棧的溢出危害很大,必定避免。因此咱們在c語言中定義局部變量時不能定義太多或者太大(譬如不能定義局部變量時int a[10000])
使用遞歸來解決問題時必定要注意遞歸收斂.
d、注意:鏈表的內存要求比較靈活,不能用棧,也不能用data數據段。只能用堆內存。
使用堆內存來建立一個鏈表節點的步驟:一、申請堆內存,大小爲一個節點的大小(檢查申請結果是否正確);二、清理申請到的堆內存;三、把申請到的堆內存看成一個新節點;四、填充你哦個新節點的有效數據和指針區域。
實例:函數
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 int main(void) 5{ 6 //建立一個鏈表節點 7 struct node *p=(struct node*)malloc(sizeof(struct node)); 8 if(NULL==p) 9 { 10 printf("malloc error.\n"); 11 } 12 //清理申請到的堆內存 13 bzero(p,sizeof(struct node)); 14 //填充節點 15 p->data=1; 16 p->pNext =NULL;//未來要指向下一個節點的首地址;實際操做時將下 一 個節點malloc返回的指針賦值給這個 17}
4、實例演示:學習
一、單鏈表的實現:spa
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 // 構建一個鏈表的節點 5 struct node 6 { 7 int data; // 有效數據 8struct node *pNext; // 指向下一個節點的指針 9 }; 10 int main(void) 11 { 12// 定義頭指針 13struct node *pHeader = NULL; 14/********************************************************************/ 15// 每建立一個新的節點,把這個新的節點和它前一個節點關聯起來 16// 建立一個鏈表節點 17struct node *p = (struct node *)malloc(sizeof(struct node)); 18if (NULL == p) 19{ 20 printf("malloc error.\n"); 21 return -1; 22} 23// 清理申請到的堆內存 24bzero(p, sizeof(struct node)); 25// 填充節點 26p->data = 1; 27p->pNext = NULL; // 未來要指向下一個節點的首地址 28 // 實際操做時將下一個節點malloc返回的指針賦值給這個 29 30pHeader = p; // 將本節點和它前面的頭指針關聯起來 31/********************************************************************/ 32/********************************************************************/ 33// 每建立一個新的節點,把這個新的節點和它前一個節點關聯起來 34// 建立一個鏈表節點 35struct node *p1 = (struct node *)malloc(sizeof(struct node)); 36if (NULL == p1) 37{ 38 printf("malloc error.\n"); 39 return -1; 40} 41// 清理申請到的堆內存 42bzero(p1, sizeof(struct node)); 43// 填充節點 44p1->data = 2; 45p1->pNext = NULL; // 未來要指向下一個節點的首地址 46 // 實際操做時將下一個節點malloc返回的指針賦值給這個 47 48 49p->pNext = p1; // 將本節點和它前面的頭指針關聯起來 50 51 52/********************************************************************/ 53 54/********************************************************************/ 55 56// 每建立一個新的節點,把這個新的節點和它前一個節點關聯起來 57 58// 建立一個鏈表節點 59 60struct node *p2 = (struct node *)malloc(sizeof(struct node)); 61if (NULL == p2) 62{ 63 printf("malloc error.\n"); 64 return -1; 65} 66// 清理申請到的堆內存 67bzero(p2, sizeof(struct node)); 68// 填充節點 69p2->data = 3; 70p1->pNext = p2; // 未來要指向下一個節點的首地址 71 // 實際操做時將下一個節點malloc返回的指針賦值給這個 72/********************************************************************/ 73// 至此建立了一個有1個頭指針+3個完整節點的鏈表。 74 75// 下面是4.9.3節的代碼 76// 訪問鏈表中的各個節點的有效數據,這個訪問必須注意不能使用p、p一、p2,而只能 77// 使用pHeader。 78 79// 訪問鏈表第1個節點的有效數據 80printf("node1 data: %d.\n", pHeader->data); 81printf("p->data: %d.\n", p->data); // pHeader->data等同於p->data 82 83// 訪問鏈表第2個節點的有效數據 84printf("node2 data: %d.\n", pHeader->pNext->data); 85printf("p1->data: %d.\n", p1->data); 86// pHeader->pNext->data等同於p1->data 87 88// 訪問鏈表第3個節點的有效數據 89printf("node3 data: %d.\n", pHeader->pNext->pNext->data); 90printf("p2->data: %d.\n", p2->data); 91// pHeader->pNext->pNext->data等同於p2->data 92 93return 0; 94}
編譯結果以下:操作系統
root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file2.c 2 root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out 3 node1 data: 1. 4 p->data: 1. 5 node2 data: 2. 6 p1->data: 2. 7 node3 data: 3. 8 p2->data: 3.
二、在鏈表末尾添加元素:
思路:由頭指針向後遍歷,直到走到原來的最後一個節點。原來最後一個節點裏面的pNext是NULL,如今咱們只要將它改爲new就能夠了。添加了以後新節點就變成了最後一個。代碼實例:
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 // 構建一個鏈表的節點 5 struct node 6{ 7int data; // 有效數據 8struct node *pNext; // 指向下一個節點的指針 9 }; 10// 做用:建立一個鏈表節點 11// 返回值:指針,指針指向咱們本函數新建立的一個節點的首地址 12struct node * create_node(int data) 13{ 14struct node *p = (struct node *)malloc(sizeof(struct node)); 15if (NULL == p) 16{ 17 printf("malloc error.\n"); 18 return NULL; 19} 20// 清理申請到的堆內存 21bzero(p, sizeof(struct node)); 22// 填充節點 23p->data = data; 24p->pNext = NULL; 25return p; 26 } 27 void insert_tail(struct node *pH, struct node *new) 28 { 29// 分兩步來完成插入 30// 第一步,先找到鏈表中最後一個節點 31struct node *p = pH; 32while (NULL != p->pNext) 33{ 34 p = p->pNext; 35// 日後走一個節點 36} 37// 第二步,將新節點插入到最後一個節點尾部 38p->pNext = new; 39 } 40 int main(void) 41 { 42// 定義頭指針 43//struct node *pHeader = NULL; 44// 這樣直接insert_tail會段錯誤。 45struct node *pHeader = create_node(1); 46insert_tail(pHeader, create_node(2)); 47insert_tail(pHeader, create_node(3)); 48insert_tail(pHeader, create_node(4)); 49 /* 50pHeader = create_node(1); 51 // 將本節點和它前面的頭指針關聯起來 52pHeader->pNext = create_node(432); 53// 將本節點和它前面的頭指針關聯起來 54 55pHeader->pNext->pNext = create_node(123); 56// 未來要指向下一個節點的首地址 57 58// 至此建立了一個有1個頭指針+3個完整節點的鏈表。 59 */ 60 // 訪問鏈表中的各個節點的有效數據,這個訪問必須注意不能使用p、p一、p2,而只能 61 // 使用pHeader。 62// 訪問鏈表第1個節點的有效數據 63printf("node1 data: %d.\n", pHeader->data); 64//printf("p->data: %d.\n", p->data); 65 // pHeader->data等同於p->data 66// 訪問鏈表第2個節點的有效數據 67printf("node2 data: %d.\n", pHeader->pNext->data); 68//printf("p1->data: %d.\n", p1->data); 69// pHeader->pNext->data等同於p1->data 70// 訪問鏈表第3個節點的有效數據 71printf("node3 data: %d.\n", pHeader->pNext->pNext->data); 72//printf("p2->data: %d.\n", p2->data); 73// pHeader->pNext->pNext->data等同於p2->data 74return 0; 75}
編譯結果:
1root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file3.c 2root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out 3node1 data: 1. 4node2 data: 2. 5node3 data: 3.
三、在第一個節點插入元素:
a、什麼是頭指針?
頭指針並非節點,而是一個普通指針,只佔4字節。頭指針的類型是struct node *類型的,因此它才能指向鏈表的節點。一個典型的鏈表的實現就是:頭指針指向鏈表的第1個節點,而後第1個節點中的指針指向下一個節點,而後依次類推一直到最後一個節點。這樣就構成了一個鏈
b、什麼是頭節點?
其實它和通常的節點差很少,只不過要注意的是:第一,它緊跟在頭指針後面。第二,頭節點的數據部分是空的(有時候不是空的,而是存儲整個鏈表的節點數),指針部分指向下一個節點,也就是第一個節點。
1 #include <stdio.h> 2 #include <strings.h> 3 #include <stdlib.h> 4 // 構建一個鏈表的節點 5 struct node 6 { 7int data; // 有效數據 8struct node *pNext; // 指向下一個節點的指針 9 }; 10 // 做用:建立一個鏈表節點 11 // 返回值:指針,指針指向咱們本函數新建立的一個節點的首地址 12 struct node * create_node(int data) 13 { 14struct node *p = (struct node *)malloc(sizeof(struct node)); 15if (NULL == p) 16{ 17 printf("malloc error.\n"); 18 return NULL; 19 } 20// 清理申請到的堆內存 21bzero(p, sizeof(struct node)); 22// 填充節點 23p->data = data; 24p->pNext = NULL; 25return p; 26 } 27 // 思路:由頭指針向後遍歷,直到走到原來的最後一個節點。原來最後一個節點裏面的pNext是NULL,如今咱們只要將它改爲new就能夠了。添加了以後新節點就變成了最後一個。 28 29 // 計算添加了新的節點後總共有多少個節點,而後把這個數寫進頭節點中。 30 31void insert_tail(struct node *pH, struct node *new) 32 { 33int cnt = 0; 34// 分兩步來完成插入 35// 第一步,先找到鏈表中最後一個節點 36struct node *p = pH; 37while (NULL != p->pNext) 38{ 39 p = p->pNext; 40 // 日後走一個節點 41 cnt++; 42} 43// 第二步,將新節點插入到最後一個節點尾部 44p->pNext = new; 45pH->data = cnt + 1; 46 } 47void insert_head(struct node *pH, struct node *new) 48{ 49// 第1步: 新節點的next指向原來的第一個節點 50new->pNext = pH->pNext; 51// 第2步: 頭節點的next指向新節點的地址 52pH->pNext = new; 53// 第3步: 頭節點中的計數要加1 54pH->data += 1; 55 } 56int main(void) 57{ 58// 定義頭指針 59//struct node *pHeader = NULL; 60 // 這樣直接insert_tail會段錯誤。 61struct node *pHeader = create_node(0); 62insert_head(pHeader, create_node(1)); 63insert_tail(pHeader, create_node(2)); 64insert_head(pHeader, create_node(3)); 65 /* 66pHeader = create_node(1); 67 68// 將本節點和它前面的頭指針關聯起來 69pHeader->pNext = create_node(432); 70// 將本節點和它前面的頭指針關聯起來 71pHeader->pNext->pNext = create_node(123); 72// 未來要指向下一個節點的首地址 73// 至此建立了一個有1個頭指針+3個完整節點的鏈表。 74 */ 75// 訪問鏈表中的各個節點的有效數據,這個訪問必須注意不能使用 p、p一、p2,而只能 76// 使用pHeader。 77// 訪問鏈表頭節點的有效數據 78printf("beader node data: %d.\n", pHeader->data); 79// 訪問鏈表第1個節點的有效數據 80printf("node1 data: %d.\n", pHeader->pNext->data); 81// 訪問鏈表第2個節點的有效數據 82printf("node2 data: %d.\n", pHeader->pNext->pNext->data); 83// 訪問鏈表第3個節點的有效數據 84printf("node3 data: %d.\n", pHeader->pNext->pNext->pNext->data); 85return 0; 86}
編譯結果:
1 root@ubuntu-virtual-machine:/mnt/hgfs/day# gcc file4.c 2 root@ubuntu-virtual-machine:/mnt/hgfs/day# ./a.out 3 beader node data: 3. 4 node1 data: 3. 5 node2 data: 1. 6 node3 data: 2.
5、總結:
經過本次鏈表的學習,讓本身對鏈表的理解更加深了,接下來雙鏈表的使用會在後面更新,歡迎你們來關注!!