日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

线性结构--离散存储 链表讲解

發布時間:2025/3/20 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 线性结构--离散存储 链表讲解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
數據結構大體成上可以分成兩種:

1. 線性結構.
2. 非線性結構( 樹,圖)

1. 什么是線性結構
?????? 大概上可以這樣定義: 加入所有的節點可以用一條直線連接起來. 就是線性結構...


2. 線性機構也可以分成兩種:
?????? 1) 連續存儲 (數組)
???????????? 也就是指每1個節點在物理內存上是相連的.

?????? 2) 離散存儲(鏈表)
????????????? 節點在物理內存上并不一定相連, 而是利用指針來關聯.

?????? 這篇文章主要講的第二種.

??????????


3. 鏈表的定義

??????? 1.多個節點離散分配.

??????? 2.彼此通過指針相連.

???????3.每個節點只有1個前驅節點, 1個后續節點.

???????4.首節點沒有前驅節點, 尾節點沒有后續節點.


???????其中第1 2點也適用于樹和圖,?? 后面3 4點就用于區別樹和圖了.



4.一些專業用語解析:

??????1) 首節點:

???????????????????第一個存放有效數據的節點.

??????

??????2)節點:

???????????????????最后一個存放有效數據的節點.尾節點的尾部指針為空


?????? 3) 節點:???????

???????????????????第1個有效節點之前的那個節點.

??????????????????? 頭節點并不存放那個有效數據,? 但是頭節點跟后面每個節點的數據類型是一樣的.

???????????????????加頭節點的目地主要是為了方便對鏈表的操作.


????????總之, 要注意頭節點并不是首節點,? 而是首節點前面的1個不存放有效數據的節點, 用于方便鏈表操作.


?????? 4)頭指針: ???????

??????????????????指向頭節點的指針, 也是頭節點的地址

?????

?????? 5)指針: ???????

??????????????????指向節點的指針, 也是節點的地址


????????????????????具體可以參考下圖所


?

5.確定1個鏈表所必須的幾個參數

??????? 首先: 如果是確定1個數組就必須知道數組的頭指針 和 數組的長度.

??????? 但是鏈表是可以根據每個節點的尾部指針一直遍歷下去, 如果遇到1個節點的尾部指針是空, 就認為它是尾節點.


??????? 如果希望通過1個函數來對數組進行處理,例如打印數組的函數,或者數組排序的函數,就要接受數組的頭部指針,和數組的長度.

???????但是如果希望通過1個函數來對鏈表進行處理,

???????只需要1個參數:

???????????????? 就是鏈表的頭部指針啦,? 因為鏈表的長度等其他信息都可以推算出來的.



6. 如何用代碼表示1個節點.

????????首先, 要明確兩點:

??????????? 1) 1個鏈表中每個節點的數據類型都是一樣的.

??????????? 2) 每個節點的數據都分兩部分(域), 1是數據部分,? 2是尾部指針.


???????????? 所以結構體的數據類型不能是常規的數據類型(int /long / char 等) , 必須是結構體, 因為結構體才能可能有多個成員嘛.

?????????????

???????????? 而每個節點的尾部指針指向的是下1個相同類型的節點, 所以尾部指針的類型和結構體的類型是必須一樣的.

struct person{int id;char name[16];struct person * pnext;};typedef struct person PERSON;

?????????????? 如上面代碼,這樣就定義了1個 簡單的PERSON 類型的節點.


7.鏈表的分類:

???????7.1 單鏈表和雙鏈表

?????????????????? 單鏈表中每1個只有1個指針域, 例如上面代碼定義的就是單鏈表的結點

?????????????????? 雙鏈表中每個指針有2個指針域,?? 其中1個指針域指向下1個節點,? 另1個指向前1個結點.


??????????

???????7.2?非循環鏈表和 循環鏈表

???????????????????? 這個容易理解,? 如果尾節點的尾部指針指向首節點, 那么它就是1個循環鏈表.



8.鏈表的一些算法:

?????? 所謂算法就是對鏈表的一些操作了.

?????? 包括和很多種, 當然我只會實現幾種最基本常用的算法:

?????????????? 新建1個鏈表

?????????????? 添加1個節點到尾部

?????????????? 插入節點

?????????????? 刪除節點

? ? ? ? ? ? ?? 遍歷

?????????????? 查找某個節點

?????????????? 清空

?????????????? 銷毀

? ? ? ? ? ? ?? 求長度

? ? ? ? ? ? ?? 排序

? ? ? ? ? ? ?


?????? 下面會對這些算法進行講解和代碼實現




9. 1個單鏈表(非循環)容器的簡單c語言實現例子



9.1. 編寫頭文件.

??????因為c語言并不面向對象語言, 所以我們不能將編寫好的鏈表容器作為1個類庫, 所以要寫個頭文件,? 那么別的文文件引用了這個頭文件, 就可以使用這個鏈表容器了.


?????頭文件代碼如下:

?????linklist1.h?

//注意bool_me.h 這是一個簡單定義布爾類型 宏的頭文件.

#include "bool_me.h" #ifndef __LINKLIST1_H_H #define __LINKLIST1_H_Hstruct person{int id;char name[16];struct person * pnext;};typedef struct person PERSON;struct Link_person{ //just a struct for linklist, it is not a node of linklistPERSON * phead; //address of the Headnode of linklistPERSON * ptail; // address of the tailnode, yes it's not a neccssity, but it can make the operation easilyint len; //numbers of the nodes which contains the userful data.it's not a neccssity, but it can make the operation easilyBOOL is_inited; // judge whether the linklist is inited};typedef struct Link_person LINKPERSON;//init a new block()PERSON * person_new(int id,char *pname);//init a LinklistLINKPERSON * link_create(int len);//judge whether the linklist is emptyBOOL link_is_empty(LINKPERSON * pLink);//add a node to the tail of LinklistBOOL link_add(LINKPERSON * plink, PERSON * pnode);//traverse the linklist to print all of the node of linklist;void link_traverse(LINKPERSON * pLink);//insert a node behind another nodeBOOL link_insert(LINKPERSON * pLink, PERSON * pBefore, PERSON * pnode);//insert a node behind another nodeBOOL link_insertbyindex(LINKPERSON * pLink, int index, PERSON * pnode);//remove a node from the linklistBOOL link_remove(LINKPERSON * pLink, PERSON * pnode);//delete a node from the linklist, and free the memory space of the nodeBOOL link_delete(LINKPERSON * pLink, int index);//get a the index of a node, if not existed, return -1int link_getindex(LINKPERSON * pLink, PERSON * pnode);//get the length of Linklistint link_getlength(LINKPERSON * pLink); //clear a LinklistBOOL link_clear(LINKPERSON * pLink);//destroy a LinklistBOOL link_free(LINKPERSON * pLink);//sort by idvoid link_sort(LINKPERSON * pLink);//get a node from linklist by indexPERSON * link_getnode(LINKPERSON * pLink, int index);#endif





????????????? 講解下,? 這里我定義了兩個結構體, 其中第1個結構體PERSON就是鏈表的節點類型

????????????? 而第2個結構體LINKPERSON是1個鏈表本身的類型, 里面包含了鏈表的第1個(頭節點)結構體的地址.? 通常這個頭節點是不存放有效數據的, 上面提過了.

? ? ? ? ? ? ? 第2個結構體里面存放了一些鏈表的關鍵信息, 雖然這些關鍵信息例如, 長度, 尾節點地址等都可以由頭節點地址推算出來, 但是畢竟遍歷推算是1個很浪費cpu時間的行為, 犧牲一些內存空間來方便運算啦


? ? ? ? ? ? ? 下面定義了若干提供給其他程序是使用的函數,? 當然我這個例子是1個極其基本簡單的例子, 也只會實現一些基本的功能的函數啦. ????????????



9.2.?處理錯誤函數voidlink_error();

?????????, 雖然只是1個簡單的實現例子, 但是國際慣例,還是盡量寫的專業一些吧..


????????代碼如下:

linklist1.c?????????? //后面的函數都寫在這個文件里

#include <stdio.h> #include <stdlib.h> #include <string.h> #include "linklist1.h"static void link_error(const char * pErr);static void link_error(const char * pstr){printf("%s\n", pstr);exit(-1); }

9.3.?創建1個新的節點函數 PERSON* person_new(int id,char* pname)

????????為什么要專門寫1個函數來創建node??

????????直接 PERSONnode1; 這樣不就創建出1個結構體節點了嗎?


???????? 沒錯, 但是這樣創建的結構體變量是靜態變量, 也就代表它所占的內存不能重復使用,? 而且當這個節點被移除時, 它所占的內存也不能被清空啊.

????????所以我會單獨寫1個函數來創建1個新的節點


????????邏輯:

???????? 1.定義1個結構體指針

???????? 2. 為這個指針分配1塊足夠的內存

?????????? 4. 結構體的各成員賦值, 尾部指針為空指針.

????????3. 返回這個指針所指向的地址.


????????代碼如下:


PERSON * person_new(int id, char * pname){PERSON * pnode;pnode = (PERSON *)malloc(sizeof(PERSON));if (NULL == pnode){link_error("fail to assign memory to PERSON");}pnode->id = id;strcpy(pnode->name, pname);pnode->pnext = NULL;return pnode; }

9.4.初始化(新建)1個鏈表函數 LINKPERSON *link_create(int len)

?????????初始化1個鏈表,? 其實這個邏輯不難理解, ?? 至于為什么有1個參數len? ? 其實就是方便用戶新建1個鏈表給這個鏈表分配數量為len的有效節點, 當然len 可以設成0啦, 這樣這個鏈表就只有1個頭節點了.

?????????無非分如下若干步:

?????????1.新建1個鏈表類型(自定義 LINKPERSON)指針

?????????2.動態給這個指針分配1個內存.

????????? 3. 動態分配1個頭節點, 地址賦給?上面的鏈表類型的第1個成員plist,? 這個成員就指向頭節點的地址. 頭節點不存放有效數據, 但是尾部指針設為空

? ? ? ? ? 4.根據len參數動態分配若干個 有效節點, 掛到頭節點后面.

???????????5. 給鏈表類型的其他成員賦值

?????????6. 最后返回這個鏈表類型的地址.


????????? 還是有點復雜啊..


代碼如下:

???????

LINKPERSON * link_create(int len){LINKPERSON * pLink = (LINKPERSON *)malloc(sizeof(LINKPERSON));if (NULL == pLink){link_error("fail to assign memory to LINKPERSON");}PERSON * phead = (PERSON *)malloc(sizeof(PERSON));if (NULL == phead){link_error("fail to assign memory to headnode");}phead->pnext = NULL;pLink->phead = phead;pLink->ptail = phead;pLink->len = 0;pLink->is_inited = TRUE;if (len==0){return pLink;}int i;char * name[16];PERSON * pnode;for(i=0;i<len;i++){char name[16];sprintf(name,"node%d",i+1);pnode = person_new(i+1,name);if (FALSE == link_add(pLink,pnode)){link_error("fail to add nodes!");}}return pLink; }

???????從代碼可以見到, 無論初始長度len是否為0 我都會有定義1個頭節點,? 然后根據len的長度循環將存放數據的有效節點掛到鏈表的尾部.

???????新建1個節點用的是 person_new() 函數, 這個函數上面寫過了.

???????而將1個節點掛到鏈表尾部我用的是link_add() 函數, 這個函數頭文件也提到的, 下面就會講解如下實現這個函數.



9.5.將1個節點添加到鏈表尾部末尾函數 BOOL link_add(LINKPERSON * pLink, PERSON * pnode)

??????首先, 這個要添加節點最好是動態分配的, 也就是說用我上面的person_new() 生成的. 否則當移除這個節點就無法釋放它的內存了.

??????而添加1個節點到鏈表尾部邏輯上是很簡單的..

???????1. 鏈表的尾節點的尾部指針指向這個要添加的節點

??????? 2.這個節點的尾部指針設為NULL.

???????3. 鏈表成員len+1


??????? 但是實際上還是有另外需要注意的問題.

???????a. 就是如何獲取尾節點???

???????? ? ? ?? 這個當然可以用頭節點逐個推算出來了.. ? 但是我文件提過, 為了方便操作,?我會把尾節點的地址也保存在鏈表類型的結構體成員中..


???????b.判斷 鏈表內是否已經有這個節點.?

??????????????? 這個就跟數組不同了. 數組是可以存放相同的數據的,例如Arr_add(10),這個函數可以重復執行.

??????????????? 鏈表呢, 因為鏈表是由節點組成的, 而唯一標識節點的是節點的地址.

???????????????假如鏈表添加1個節點, 但是這個節點的地址已經在這個鏈表中的話就出現下圖中的問題了:


???????????????????? 如下圖:


??????????所以這個函數會判斷下這個節點是否在鏈表中, 還是要遍歷一次啊..??有更好方法的可以告訴我..

代碼如下:



BOOL link_add(LINKPERSON * pLink, PERSON * pnode){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if (NULL == pnode){printf("pnode is empty!\n");return -1;}//judge whether pnode is existed in the linklist alreadyif (link_getindex(pLink,pnode) > -1){printf("the node is existed in linklist already!\n");return FALSE;}pLink->ptail->pnext = pnode;pnode->pnext = NULL;pLink->ptail = pnode;pLink->len++;return TRUE; }

可以見到, 我并沒有每次都遍歷求出 尾節點的地址, 而是把尾節點地址放到 鏈表類型的1個成員中, 方便操作啊~


上面所以說遍歷了1次, 似乎因為執行力獲取節點序號函數? link_getindex(),如果這個節點不存在, 則返回0. 下面就是這個函數的寫法.


9.6.獲取1個節點在鏈表中的位置.??? intlink_getindex(LINKPERSON * pLink,PERSON * pnode)

???????注意這個函數也用與判斷節點是否存于鏈表中,? 如果存在就返回位置index, 不存在就返回-1.

???????邏輯上也很清楚,? 沖首節點起(不是頭節點), 逐個判斷, 直到到達尾節點, 這個過程不需要關心鏈表的長度.

???????而且這個遍歷不包括頭節點,所以如果把頭節點作為參數, 一樣會返回 -1,因為頭節點不是存放數據的有效節點.


??????? 代碼如下:


int link_getindex(LINKPERSON * pLink, PERSON * pnode){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if (NULL == pnode){printf("pnode is empty!\n");return -1;}PERSON * pn = pLink->phead;int i=0;while (NULL != pn->pnext){if (pn == pnode){return i;}i++;pn = pn->pnext;}return -1; }

?

9.7. 判斷鏈表是否阿為空 BOOL link_is_empty(LINKPERSON * pLink).

????????這個就毫無難度啦,只需判斷鏈表內是否只有頭節點就ok了.

?????????當然, 為了方便操作, 也可以判斷 鏈表成員len 是否等于0

BOOL link_is_empty(LINKPERSON * pLink){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if (NULL == pLink->phead->pnext){return TRUE;}return FALSE; }

9.8.?遍歷輸出鏈表函數. void link_traverse(LINKPERSON * pLink)

????????這個要注意的是, 實際上就是遍歷鏈表, 然后逐個輸出節點.??

????????? 只需定義1個指針,首先指向首節點(頭節點的下1個), 然后輸出.

????????? 再把這個指針指向被輸出的指針的后1個, 繼續輸出,直到尾部指針為空(尾節點).

?????????所以這個過程根本不關心鏈表的長度的.

?????????

void link_traverse(LINKPERSON * pLink){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if (TRUE == link_is_empty(pLink)){printf("the linklist is emptyi!");return;}PERSON * pnode;pnode = pLink->phead; //pheadwhile(NULL != pnode->pnext){pnode = pnode->pnext;person_print(pnode);}return; }


9.9.?打印1個節點的函數. void?person_print(PERSON * pnode)

???就是上面用的person_print(pnode)啦, 太簡單不講解


static void person_print(PERSON * pnode){printf("id is %d, name is %s\n", pnode->id, pnode->name ); }

9.10.?根據序號獲取1個節點函數 PERSON * link_get(LINKPERSON * pLink, int index)

? ? ? ?相對來講,這個函數就跟簡單了。

? ? ? ?首先, 判斷參數 index 的范圍, 如果少于0 , 或者大于鏈表當前個數-1 , 則返回1個空指針。

? ? ? ?鏈表的個數怎么求? ?這時我鏈表類型的len成員就發揮作用了..雖然作用意義不是很大.

? ? ? ?然后根據index的值 ?遍歷若干次就得到想找的節點地址啦。


? ? ? ?代碼如下:

PERSON * link_getnode(LINKPERSON * pLink, int index){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if ( index < 0 || index > (pLink->len -1)){printf("index is over the limit!\n");return NULL; }int i;PERSON * pnode = pLink->phead; for( i=0;i <= index ;i++){pnode = pnode->pnext;} return pnode; }


9.11.插入1個節點到另1個節點之后的函數 link_insert(LINKPERSON *pLink, PERSON * pBefore, PERSON * pnode);

?????通常來講,? 鏈表是整個數據結構的重點,? 而插入節點的操作就是鏈表的重點..

????? 講下原理,???假如要將參數中的 pnode 插入到 pBefore的后面.

?????假如原來的pBefore 的后面是 pAfter.

?????那么pBefore->pnext == pAfter?? 這個很簡單.??

?????? 而現在要把pnode 放到 pBefore 和pAfter 之間.? 則pnode 在 pAfter前面

??????? 所以要執行:

????????????????

pnode->pnext = pBefore->pnext //相當與 pnode->pnext = pAfter
??????????

???????? 然后pBefore 的后面是pnode, 所以再執行

pBefore->pnext = pnode



??????? 就完成插入了.



???????? 那么對于這個函數, 邏輯上包括如下幾點:

??????? 0. 判斷pBefore 和 pnode 不是空指針

??????? 1. 判斷 pBefore 是否存在于鏈表中, 如果不存在, 返回false.

??????? 2. 判斷 pnode 是否已經存在于鏈表中, 如果是, 返回false.

??????? 3. 把pnode 插到pBefore 后面

??????? 4. 如果pnode 是最后1個節點.? 則pLink的成員 ptail = pnode.

??????? 5. pLink->len++

??????? 6. return true.


代碼如下

BOOL link_insert(LINKPERSON * pLink, PERSON * pBefore, PERSON * pnode){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if (NULL == pBefore || NULL == pnode){printf("pBefore or pnode is empty!\n");return FALSE;}if (0 > link_getindex(pLink,pBefore)){printf("pBefore is not existed in linklist!\n");return FALSE;}if (-1 < link_getindex(pLink,pnode)){printf("pnode is existed in linklist already!\n");return FALSE;}pnode->pnext = pBefore->pnext;pBefore->pnext = pnode;if (NULL == pnode->pnext){pLink->ptail = pnode;}pLink->len++;return TRUE; }




9.12. 插入1個節點到制定鏈表的位置link_insertbyindex(LINKPERSON *pLink, int index, PERSON * pnode);

????????這個函數是把1個節點插入到鏈表的第 index? 個節點后面(index 由0(首節點開始));

????????這個需要做的事情如下:


????????就是調用上面的函數啦?

???????? link_insert(pLink, link_getnode(index), pnode);???????? ...


???????但是假如? index 不是一個有效的數據, 比如超出了鏈表范圍??

???????那么 link_getnode(index) 就會返回空指針,? 而 link_insert 函數接收到空指針參素就會返回FALSE啊~


???????? 代碼如下:

BOOL link_insertbyindex(LINKPERSON * pLink, int index, PERSON * pnode){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}return link_insert(pLink, link_getnode(pLink, index), pnode); }


9.13.將1個節點移除出鏈表BOOL link_remove(LINKPERSON * pLink, PERSON *pnode);

??????見到我的頭文件中又有remove又 有delete? 其實他們作用并不相同,

?????? 首先,? remove 函數只會將節點pnode 移鏈表, 不會釋放這個節點的內存(free), 因為用戶很可能還會將這個放到鏈表的其他地方或者放入另1條鏈表.


?????? 而delete 函數直接會將 具體位置的節點移除鏈表后,并銷毀 , 釋放出內.


?????? 至于怎樣移除出1個節點pnode?

???????? 1. 如果節點不再鏈表中,? 返回FALSE

??????2.pnode 前1個節點指向 pnode->pnext

?????? 3.如果前1個節點的pnext 是空, 則帶代表pnode 是1個尾節點,?把尾節點移除后,? pLink的成員ptail 的值改成pnode的前1個節點


??????問題來了, 怎樣找到pnode 的前1個節點 pBefore, 如果是雙鏈表, 直接可以由指針找到, 但是這個是鏈表..


??????當然, 可以先用link_getindex(pnode) 獲得 pnode 的 index,? 再用link_getnode(index-1) 獲得 pBefore,但是這樣就執行兩次遍歷了, 為了性能著想, 還是直接遍歷吧..


?????? 代碼如下:

BOOL link_remove(LINKPERSON * pLink, PERSON * pnode){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if (NULL == pnode){printf("pnode is empty!\n");return -1;}PERSON * pBefore = pLink->phead;while (NULL != pBefore->pnext ){if (pBefore->pnext == pnode){pBefore->pnext = pnode->pnext;if (NULL == pBefore->pnext){ // pnode is ptail;pLink->ptail = pBefore;pLink->len--;return TRUE;}}pBefore = pBefore->pnext;}return FALSE; //pnode is not existed in the linklist before. }


9.14將1個節點移除出鏈表并釋放內存BOOL link_delete(LINKPERSON *pLink,int index);

??????好吧, 這個函數實際上就是上面那個remove 函數, 然后再手動釋放內存..

?????? 邏輯如下:

?????? 1. 利用link_getnode() 函數獲得要remove 的節點

??????2. 利用link_remove 移除這個節點.

?????? 3. 手動釋放內存.


?????? 代碼如下:

BOOL link_delete(LINKPERSON * pLink, int index){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}PERSON * pnode = link_getnode(pLink, index);if (NULL == pnode){return FALSE;}if (TRUE == link_remove(pLink, pnode)){free(pnode);return TRUE;}return FALSE;}

9.15? 清空1張鏈表 BOOL link_clear(LINKPERSON * pLink);

???清空鏈表的意識就是把鏈表所有的節點移除?

??? 那么可以循環執行 link_remove嗎??

???執行一次link_remove 就遍歷1次啊...


??? 其實清空1張鏈表 根本不需要遍歷:

??? 直接將頭節點的尾部指針設為NULL 就ok啦!


代碼如下:

BOOL link_clear(LINKPERSON * pLink){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}pLink->phead-> pnext = NULL;pLink->ptail = pLink->phead;pLink->len=0;return TRUE; }



9.16?銷毀1張鏈表, BOOL link_clear(LINKPERSON * pLink);


??這個函數跟上面完全不同啊, 沒那么簡單~

?? 首先這個函數分兩步,? 首先釋放所有節點的內存,

??然后釋放這個鏈表類型(結構體)指針本身.


???第2步很簡單? 直接free(pLink) 就ok了,? 問題如何執行第一步呢?

??? 循環去link_delete嗎?? 問題每執行一次link_delete 就執行1此 link_remove里面每次都遍歷1次啊..


???為了避免渣性能, 還是手動循環去釋放節點吧..

???? 這里有個問題了, 到底是從首節點開始釋放內存?? 還是由尾節點開始釋放?

???? 如果從尾節點開始釋放,? 你把尾節點釋放后, 怎么找前1個節點? 除了遍歷還真沒有辦法..


????? 從首節點釋放后, 怎么找下1個節點?? 這個就簡單啊, 釋放首節點前保存它的部節點指針就是了.

????? 所以我們應該從首節點釋放


???? 代碼如下:

BOOL link_free(LINKPERSON * pLink){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}PERSON * pnode = pLink->phead;PERSON * pAfter =pnode->pnext;//printf("free pnode which id is %d\n",pnode->id);free(pnode); //free pheadwhile(NULL != pAfter){pnode=pAfter;//printf("free pnode which id is %d\n",pnode->id);pAfter = pnode->pnext;free(pnode);}free(pLink);return TRUE;}


9.17鏈表排序算法(根據id成員的值) void link_sort(LINKPERSON * pLink)

????當然這里還是講解下最簡單的冒泡排序法了,??

???? 冒泡排序法在對于數組來講是十分容易實現的, 基本上都能背出來了~

???? 代碼如下:

int i,j,m;for (i=0; i<len - 1; i++){for(j=i+1;j<len; j++){if (a[i] > a[j]){m = a[i];a[i]=a[j];a[j]=m;}} }

原理就是循環求出數組最小的值的元素, 放在最左邊, 然后求出第二小的元素, 放在數組第2位.....

這里關鍵就是每比較一次, 如果左邊的值大于右邊的值

這個兩個元素就會互相交互它們的值.



對于鏈表來講, 實現原理也差不多的..


1.

數組中用i, j兩個變量來存儲要比較的元素的位置.

那么在鏈表中, 我們可以定義2個指針pPre, pAfter來存儲要比較節點的位置


2.

數組中i的初始是第1個元素, 所以i=0 開始

對于i的每1個值.?? j初始話是i后面1個元素. 所以j從 j=i+1 開始


那么對于鏈表來講. pPre就從首節點開始,? 所以pPre= phead->pnext? (phead 是頭節點)

pAfter 從第pPre的后1個節點開始, 所以pAfter= pPre->pnext 開始.


3.

數組中每執行1次循環, i 或j的值加1, 表示執行她們的下1個元素


鏈表中, 每執行1次循環. pPre 或 pAfter的地址指向下1個節點. 所以p=p->pnext


代碼如下:

void link_sort(LINKPERSON * pLink){if (TRUE != pLink->is_inited){link_error("the linklist is not inited yet");}if (pLink->len < 2){return;}PERSON * pPre; //pheadPERSON * pAfter;PERSON * pPB; //used to save the node address which before the pPrePERSON * pAB; //used to save the node address which before the pAfterPERSON * m;for (pPB=pLink->phead,pPre=pLink->phead->pnext; NULL != pPre->pnext; pPB=pPre,pPre=pPre->pnext){for (pAB=pPre,pAfter=pPre->pnext; NULL != pAfter; pAB=pAfter,pAfter=pAfter->pnext){if (pPre->id > pAfter->id){link_exchange(pPB,pAB);m = pPre;pPre = pAfter;pAfter = m;};}} }

注意上面, 我還定義多兩個指針, 專門指向, pPre 和 pAfter 的前1個節點地址,

就是pPB->pnext == pPre??????? pAB->pnext ==pAfter


為什么這樣做, 是因為單鏈表中無法由1個節點求出上1個節點的地址(只能根據尾部指針得到下1個節點的地址)


在數組中,?

如果比較一次? a[i] > a[j]? 之后在就會交換它們的值,


鏈表比較 pPre->id >pAfter->id 后, ? 當然也可以互相交換他們的id值, ??

例如

int m=pPre->id; pPre->id = pAfter->id; pAfter->id =m;char n[16]=pPre->name; pPre->name=pAfter->name; pAfter->name=n;

但是對于1個節點來講, 地址沒有變化, 但是成員值變化了.

實際應用中? , 大部分希望1個節點里面的成員值不會產生變化, 而是節點本身在鏈表中的位置變化

而且當1個節點的成員非常多時,? 交換全部成員的值的成本往往大于交換他們在鏈表中位置的成本.


我用了

link_exchange(pPB,pAB);這個函數來交換pPre, 和pAfter的地址,

但是注意傳的參數是他們的上1個節點, 而是他們本身.

為什么呢, 下面會講解這個函數.


假如 pPre指向的是鏈表第2個節點,? pAfter 指向的是第4個節點

那么交換節點后,? pPre 就指向鏈表中第4個節點了,? 而pAfter 指向第2個節點了.? 因為他們指向的節點在鏈表中的位置換了嘛...

所以根據冒泡排序法的原理, pPre 要指向前面的節點, 而pAfter 要指向后面的節點.


所以我們還要把 pPre 和 pAfter所指向的地址互換.? 讓pPre 指回第2個節點. 而pAfter 指回第4個節點.

所以要執行

m = pPre; pPre = pAfter; pAfter = m來交換他們的地址啊.



9.18鏈表交換兩個節點的下1個節點函數BOOL link_exchange(LINKPERSON*pPB, LINKPERSON * pAB)

?????當然, 可以先執行link_remove 再 link_insert到適當的位置達到目地, 但是就執行了若干次無謂的遍歷動作, 不推薦!


?????注意的是, 這個函數交換的不是pPB 和 pAB的位置,? 是交換 pPB->pnext 和 pAB->pnext 的位置.

?????為什么要這樣呢, 看到后面就明白了.


首先圖解交換兩個節點位置要做的操作:


假如我要交換下圖中節點2 和 節點4 的位置.


1.首先節點2的前1個節點1的尾部指針指向節點4




2.然后節點4原本的前1個節點3的尾部指針指向節點2

'


3.然后節點2的尾部指針指向節點4的尾部指針地址(節點5)


4.然后節點4的尾部指針指向節點2原本尾部指針地址(節點3)




好了, 經過上面4部后就完成節點2和節點4的位置交換了, 原來順序是1 2 3 4 5,???? 后來根據指針就是1 4 3 2 5啦


經過總結, 可以發現上面4步實際上可以總結成為2步:

1. 交換節點2和節點4的前1個節點,???? 也就是節點2 和 節點4 的前1個節點的尾部指針的值互換

2. 交換節點2和節點4的后1個節點 ,??? 也就是節點2 和 節點4 的尾部地址的值互換


第2步很簡單?

無非就是? p2->pnext? 和 p4->pnext 互換

問題來了,

第1步怎么實現呢,?? 因為根據p2 和 p4 無法知道 p1 和 p3的地址啊,?? 單鏈表只能單向求1下1個節點地址,? 而不能往前退的.

所以!

'我們在這個函數中, 參數不要定為 p2 和 p4 ,? 而是把他們的前1個節點? p1 和? p3 作為參數傳入來

那么

p2==p1->pnext

p4==p3->pnext?


那么第1步?? 把 p1->pnext 和 p3->pnext 的值互換就ok啦

第二步呢,?? 因為p1->pnext 和? p3->pnext 的值互換過了.

所以p1->pnext 就是p4啊??? p3->pnext 就是p2啊?? ,? 而我們上面見過第2步是 p2->pnext 的值和 p4->pnext的值互換

所以就是 p3->pnext->pnext 的值 和 p1->pnext->pnext 的值互換啊!


代碼如下:

static void link_exchange(PERSON * pPB, PERSON * pAB){//this function will exchange the place of the next nodes of pPB and pAB//if pPB->pnext == pPre ; pAB->pnext == pAfter//after exchange, pPre and pAfter will exchange their index in the linklistPERSON * m;//first , exchange their Pre node;m = pPB->pnext;pPB->pnext = pAB->pnext;pAB->pnext = m;//then . exchange their next node;m=pPB->pnext->pnext;pPB->pnext->pnext = pAB->pnext->pnext;pAB->pnext->pnext = m; }


9.19寫個小程序來測試一下這個鏈表容器



其實就是寫1個c文件, 引用這個鏈表容器的頭文件,調用我上面寫的函數,就是可以測試了


代碼如下啦:


int link_1(){//const char *n = "gateman poon";PERSON * p1 = person_new(1,"Jason Poon" );LINKPERSON * plink1 = link_create(10);//LINKPERSON * plink2;//link_traverse(plink2);//PERSON * p3;//printf("id is %d, name is %s\n",p3->id, p3->name);free(p1);p1 = link_getnode(plink1, 3);link_insert(plink1, link_getnode(plink1,4), person_new(24, "Gateman"));link_insertbyindex(plink1, 5, person_new(11, "Nedved"));link_remove(plink1, link_getnode(plink1,7));link_delete(plink1, 7);link_add(plink1, person_new(12, "Cindy"));link_add(plink1, person_new(24, "Gateman"));link_add(plink1, person_new(11, "Nvd11"));link_add(plink1, person_new(49, "Lulu"));link_add(plink1, person_new(47, "Alice"));link_traverse(plink1);printf("will be sort now!\n\n");link_sort(plink1);link_traverse(plink1);//link_clear(plink1);link_free(plink1);//link_traverse(plink1);//printf("id is %d, name is %s\n",p1->id, p1->name);printf("link1 done\n");return 0;}
輸出:



10,一些總結,

數組的優點:

????????? 存取速度快, 因為根據頭部指針就可以根據下標定位到對應的元素.

????????? 缺點:

????????? 實現必須知道數組的長度

????????? 需要連續的內存空間

????????? 插入和刪除速度慢. 一旦插入或刪除1個元素, 意味這大量元素要向前或向后移動1位.


鏈表的優點:

????????? 空間幾乎不用限制, 而數組必須要有連續的內存空間

????????? 插入和刪除元素的速度很快, 只需要修改部分指針.

鏈表缺點:

????????? 存儲速度慢. 因為要找1個元素就必須一直遍歷下去..



??? 本文的函數就寫到這里了,? 其實還有很多功能沒有實現, 例如求鏈表有效長度(這個太簡單),?? 兩個鏈表的對接(這個...也不難啦)等..? 怎么說呢, 雖然偽算法不難看懂, 但是真正用代碼來實現還是有點復9啊...

??? 關鍵就是對于指針的理解了, 對于數據結構, 指針機會就是一切啊....




總結

以上是生活随笔為你收集整理的线性结构--离散存储 链表讲解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。