C語言數據結構單鏈表接口函數全面講解教程
前言
上一期數據結構專欄我們學習瞭順序表後,在運用時,細心的同學可能會發現,如果要頭插、尾插或者任意位置。如果原先的空間已經被占滿瞭,你是需要擴容的,動態鏈表擴容往往是2倍,但是擴容後,如果後面沒有使用完全擴容後空間就會造成空間浪費,為瞭解決這個問題,我們今天將學習鏈表。
提示:以下是本篇文章正文內容,下面案例可供參考
一、鏈表的概念及結構
1.概念
鏈表是一種物理存儲結構上的非連續。非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。
(圖片來自比特就業課)
上圖是一個普通鏈表,它物理上是不連續的,那怎麼讓這些數據建立聯系呢?鏈表每個位置會存放兩個數據——1個是數據,另1個是指針
typedef int SLTDataType;//這裡是方便將來改數據類型,在這裡把int修改一下即可 typedef struct SlistNode//一個位置放多個數據用結構體 { int data; struct SlistNode*next;//指針是指向下一個節點,下一個節點也是結構體,所以這裡是結構體指針 }SLTNode;//將結構體類型名用SLTNode重命名
我們用一張圖來深入瞭解一下它的邏輯結構:
圖示是鏈表的三個相連元素,第一個元素存放數據1和第二個元素的地址,然後以此類推。那到這裡可能有同學會問:“那每個都是有存放下一個地址,最後一個節點存放誰的地址?”是這樣的,最後一個節點存放的是NULL空指針,空指針也是作為鏈表結尾的標記
到這裡,我們知道,鏈表的每個節點放的是當前節點內容和下一個節點的指針,那我們怎麼找到這條鏈表呢?畢竟你第一個節點放的是第二個節點的指針,事實上,我們會定義一個指針指向第一個節點,以此來確定該鏈表
二、鏈表的使用
1.遍歷整個鏈表
代碼如下(示例):
void SListPrint(SLTNode*phead) { SLTNode*cur = phead; while (cur != NULL) { printf("%d", cur->data); cur = cur->next; } }
我們以這張圖來理解一下這段代碼,我們創建一個結構體指針cur,並把鏈表首元素地址賦給cur,也就是說,cur指向瞭鏈表的首節點,我們知道首節點裡是有第二個節點的地址的,我們通過cur = cur->next;可以找到第二段節點,以此類推。
2.尾插
(圖片來自比特就業課)
想要尾插一個節點,我們首先要malloc(擴容函數)一塊空間出來,開辟出那塊空間之後,要把新節點與鏈表連接起來,就需要鏈表尾部節點的地址,我們用循環遍歷得到,然後把新節點地址賦給之前鏈表的尾部節點即可
代碼如下(示例):
void SListPushBack(SLTNode**pphead, SLTDataType x) { SLTNode*newnode = (SLTNode*)malloc(sizeof(SLTNode)); //開辟一塊大小為一個SLTNode類型的空間出來,並強制轉換成結構體指針賦給newnode newnode->data = x; newnode->next = NULL; if (*pphead == NULL)//防止一開始鏈表裡面什麼也沒有, {//由於pphead是頭節點的地址的地址,*解引用一下得到頭結點的地址 *pphead = newnode; } else { SLTNode*tail = *pphead; while (tail->next != NULL)//尋找尾節點的指針 { tail = tail->next; } tail->next = newnode;//新增的節點地址賦給尾結點存儲 } }
這裡為什麼用二級指針傳參?解釋如下:
我們進行尾插時,要先找到鏈表所在嘛,然後這個是靠鏈表頭節點地址確定的,你傳瞭一個地址過去,註意這個地址是實參,你實參過去函數裡會再創建一個形參也是這個地址,然後進行操作,改變的是形參裡的東西,實參不會受影響。這裡也就是傳值調用和傳址調用的區別,為瞭形參可以影響到實參,我們用傳址調用,也就是傳地址的地址
3.頭插
(圖片來自比特就業課)
我們假設現在要在一個鏈表前面插入0這個數據(如上圖所示),0所在地址為0x0006708,那是不是要把原鏈表首節點地址放到0這個節點裡面,然後頭節點地址更新為0這個新節點的地址。如下圖:
ps:plist/phead表示鏈表首節點地址
代碼如下(示例):
void SListPushFront(SLTNode**pphead, SLTDataType x) { SLTNode*newnode = (SLTNode*)malloc(sizeof(SLTNode)); //開辟一塊大小為一個SLTNode類型的空間出來,並強制轉換成結構體指針賦給newnode newnode->data = x;//插入節點的數據 newnode->next = *pphead;//把原先節點首元素地址賦給新節點 *pphead = newnode;//新節點首元素地址用新插入的節點地址賦值 }
4.頭刪
關於頭刪,很多同學可能有一種想法,就是直接把頭結點free(對動態分配的內存進行釋放)掉,剩下的就是我們需要的鏈表。但這裡會有一個問題,就是你把頭節點去瞭,因為鏈表是用地址連接的,我們原本是用頭節點地址找到該鏈表,現在頭節點去掉瞭,我們怎麼知道剩下的鏈表在哪裡。所以這裡的解決方案是,先把第二個節點地址保存起來然後再釋放掉頭節點
代碼如下(示例):
void SListPopFront(SLTNode**pphead) { SLTNode*next = (*pphead)->next; free(*pphead); *pphead = next; }
5.尾刪
尾刪和頭刪有同樣的問題,我們能不能直接把尾部節點給free掉?答案也是不可以的,同學你細想一下,尾部節點是不是連接著倒數第二個節點,而倒數第二個節點保存著尾結點地址,你把尾結點free掉瞭,那倒數第二個節點保存的就是野指針瞭,這顯然是不行的,所以我們需要把倒數第二個節點存儲的指針變成空指針,然後free掉尾結點
void SListPopBack(SLTNode**pphead) { if (*pphead == NULL)//如果節點本來就沒有,那就沒有刪除的說法瞭 { return; } else if ((*pphead)->next == NULL)//如果本來鏈表隻有一個節點,你刪除一個,之前會有一個指針指向首節點來記錄這個鏈表,你現在把唯一的節點刪除瞭,那個記錄鏈表的指針就成野指針瞭 { free(*pphead); *pphead = NULL; } else { SLTNode*prev = NULL;//prev為tail前個節點指針 SLTNode*tail = *pphead; while (tail->next != NULL) { prev = tail; tail = tail->next; } free(tail); prev->next = NULL; } }
這裡還是有註意的點的,比如你要刪除的鏈表本來就是空鏈表,刪除就無從談起瞭,還有就是原先鏈表隻有一個節點,你刪除一個,之前會有一個指針指向首節點來記錄這個鏈表,你現在把唯一的節點刪除瞭,那個記錄鏈表的指針就成野指針瞭
6.任意位置插入數據
上圖紅色是原有鏈表,我們要在2和3直接插入一個30怎麼做?首先我們要把2、30、3這3個節點連接起來,也就是說,2節點要指向30這個節點,30這個節點要指向3這個節點。這裡如何操作呢?我們需要設計一個函數先找到2節點和3節點的地址,然後進行插入操作
查找函數和插入函數如下
SLTNode* SListFind(SLTNode*phead, SLTDataType x)//查找鏈表 { SLTNode*cur = phead; while (cur)//非空指針則進行循環 { if (cur->data == x)//給定鏈表中一個數據進行查找 { return cur;//返回所查找位置指針 } cur = cur->next; } return NULL; } void SListInsert(SLTNode**pphead,SLTNode*pos, SLTDataType x)//在pos前插入x,x是要插入的數 { if (pos == *pphead)//原鏈表隻有1個節點 { SListPushFront(pphead, x); } else { SLTNode*newnode = (SLTNode*)malloc(sizeof(SLTNode)); //開辟一塊大小為一個SLTNode類型的空間出來,並強制轉換成結構體指針賦給newnode newnode->data = x; newnode->next = NULL;//開辟1個新節點 SLTNode*prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = newnode; newnode->next = pos;//把新節點連接上去 } }
兩個函數一起調用是這樣的
if (pos)//防止該位置是空指針 { SlistInsert(&plist, pos, 30); } SListPrint(&plist); }
7.任意位置刪除數據
比如說我們現在要把3刪除,那這裡就會出現一個需要:3刪除瞭,要把2和4連接起來,然後把3節點釋放掉
void SListErase(SLTNode**pphead, SLTNode*pos)//刪除pos位置的值 { SLTNode*prev = *pphead; while (prev->next != pos)//找到3節點的位置 { prev = prev->next; } prev->next = pos->next;//prev是2,pos是3,pos->next是4,要把4接上2 free(pos);//2和4接上之後就可以free掉3瞭 }
ps:上述prev是2,pos是3這種是方便同學你理解,並不特指它真的是2和3
然後這裡進行函數調用的時候,依然需要進行find定位一下需要刪除位置地址
SLTNode*pos = SListFind(plist, 3); if (pos)//防止該位置是空指針 { SListErase(&plist, pos); }
後記
鏈表是數據結構非常重要的一塊知識,本文著重介紹瞭鏈表的接口函數,下一期筆者會更新特殊鏈表的使用,點贊三連可以加快更新速度哦~更多關於C語言數據結構單鏈表接口函數的資料請關註WalkonNet其它相關文章!