C++超詳細(xì)講解單鏈表的實(shí)現(xiàn)
單鏈表的實(shí)現(xiàn)(從入門到熟練)
概念和結(jié)構(gòu)
概念:鏈表是一種物理存儲(chǔ)結(jié)構(gòu)上非連續(xù)、非順序的存儲(chǔ)結(jié)構(gòu)
數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈 接次序?qū)崿F(xiàn)的
圖示:

注意:
鏈表結(jié)構(gòu)在邏輯上為連續(xù)的,但是物理上(內(nèi)存中)不一定連續(xù)
鏈表節(jié)點(diǎn)都是在堆上申請(qǐng)出來的,申請(qǐng)空間按一定策略分配
結(jié)構(gòu)種類
鏈表具有多種結(jié)構(gòu):單向\雙向,帶頭\不帶頭,循環(huán)\非循環(huán)
實(shí)際上最常用的是:無頭單向非循環(huán)鏈表,帶頭雙向循環(huán)鏈表
鏈表的實(shí)現(xiàn)
注意:這里實(shí)現(xiàn)的是無頭單向非循環(huán)鏈表
增刪查改接口
// 動(dòng)態(tài)申請(qǐng)一個(gè)節(jié)點(diǎn) SListNode* BuySListNode(SLTDateType x); // 單鏈表打印 void SListPrint(SListNode* plist); // 單鏈表尾插 void SListPushBack(SListNode** pplist, SLTDateType x); // 單鏈表的頭插 void SListPushFront(SListNode** pplist, SLTDateType x); // 單鏈表的尾刪 void SListPopBack(SListNode** pplist); // 單鏈表頭刪 void SListPopFront(SListNode** pplist); // 單鏈表查找 SListNode* SListFind(SListNode* plist, SLTDateType x); // 單鏈表在pos位置之后插入x void SListInsertAfter(SListNode* pos, SLTDateType x); // 單鏈表刪除pos位置之后的值 void SListEraseAfter(SListNode* pos);
節(jié)點(diǎn)結(jié)構(gòu)體創(chuàng)建
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
節(jié)點(diǎn)開辟
對(duì)于鏈表來說,每需要空間就需要進(jìn)行開辟,這里我們將之封裝成一個(gè)函數(shù),便于后續(xù)調(diào)用直接使用(開辟的同時(shí)進(jìn)行賦值)
參考代碼:
//鏈表節(jié)點(diǎn)開辟
SLTNode* BuySListNode(SLTDateType x)
{
//動(dòng)態(tài)分配一個(gè)節(jié)點(diǎn)
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
//分配失敗則打印錯(cuò)誤信息并結(jié)束進(jìn)程
perror("newnode fail:");
exit(-1);
}
//成功則進(jìn)行賦值
newnode->data = x;
newnode->next = NULL;
//返回節(jié)點(diǎn)地址,以便后續(xù)操作
return newnode;
}
數(shù)據(jù)打印
注意:
1.對(duì)于不帶頭的鏈表來說,打印數(shù)據(jù)不需要修改鏈表首節(jié)點(diǎn)地址(故只要傳鏈表指針)
2.用循環(huán)遍歷鏈表,每打印數(shù)據(jù),需要指向下一個(gè)節(jié)點(diǎn)
3.依靠尾節(jié)點(diǎn)的址域?yàn)镹ULL來結(jié)束循環(huán)
代碼:
//鏈表數(shù)據(jù)打印
void SListPrint(SLTNode* phead)//一級(jí)指針接收一級(jí)指針(打印不需改變指針本身內(nèi)容)
{
//創(chuàng)建一個(gè)尋址指針
SLTNode* cur = phead;
while (cur!=NULL)//后續(xù)還有節(jié)點(diǎn)
{
//打印數(shù)據(jù)并指向下一個(gè)節(jié)點(diǎn)
printf("%d->", cur->data);
cur = cur->next;
}
//最后打印空指針
printf("NULL\n");
return;
}
鏈表尾插數(shù)據(jù)
- 要尾插數(shù)據(jù)則需要遍歷找到鏈表的尾節(jié)點(diǎn)
- 對(duì)于不帶頭鏈表,尾插數(shù)據(jù)也可能是插在鏈表首元素的地址(當(dāng)鏈表為空),需要修改鏈表指針的內(nèi)容(故需要傳入鏈表指針的地址)
- 插入數(shù)據(jù)要開辟節(jié)點(diǎn)
代碼:
//鏈表尾插數(shù)據(jù)
void SListPushBack(SLTNode** pphead, SLTDateType x)//二級(jí)指針接收一級(jí)指針(尾插存在需改變鏈表指針本身內(nèi)容)
{
//避免傳入錯(cuò)誤(直接報(bào)錯(cuò)便于找到錯(cuò)誤位置)
assert(pphead);
//接收新節(jié)點(diǎn)的地址
SLTNode* newnode=BuySListNode(x);
//頭節(jié)點(diǎn)為空
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾節(jié)點(diǎn)
SLTNode* tail = *pphead;//創(chuàng)建尋址節(jié)點(diǎn)
//兩個(gè)及以上節(jié)點(diǎn)的情況
while (tail->next != NULL)
{
//指向下一個(gè)節(jié)點(diǎn)
tail = tail->next;
}
//找到了
tail->next = newnode;
}
return;
}
注意代碼中的assert的作用:
- 正確傳入鏈表指針的地址是不會(huì)為空的
- 但是對(duì)于非正常傳入鏈表指針(不是鏈表指針的地址)且此時(shí)鏈表指針為空則會(huì)發(fā)生報(bào)錯(cuò)(assert報(bào)錯(cuò)會(huì)告訴錯(cuò)誤位置),告訴程序員應(yīng)該傳入鏈表指針的地址
頭刪
注意:
- 刪除前需要保存當(dāng)前節(jié)點(diǎn)的址域(即保存下個(gè)節(jié)點(diǎn)的空間地址,以防丟失)
- 前刪數(shù)據(jù)即刪除當(dāng)前鏈表首節(jié)點(diǎn),即需修改鏈表指針的內(nèi)容(故需傳入鏈表指針的地址)
- 刪除后修改鏈表指針內(nèi)容,指向新的首節(jié)點(diǎn)
- 如果鏈表為空時(shí)無法刪除(保存下個(gè)節(jié)點(diǎn)地址會(huì)造成非法訪問)
代碼:
//鏈表前刪數(shù)據(jù)
void SListPopFront(SLTNode** pphead)
{
//避免傳入錯(cuò)誤(直接報(bào)錯(cuò)便于找到錯(cuò)誤位置)
assert(pphead);
//鏈表為空的情況
if (*pphead == NULL)
{
return;
}
//創(chuàng)建指針保存第二個(gè)節(jié)點(diǎn)地址
SLTNode* next = (*pphead)->next;
//釋放當(dāng)前頭結(jié)點(diǎn)空間
free(*pphead);
//更新頭結(jié)點(diǎn)數(shù)據(jù)
*pphead = next;
return;
}
鏈表數(shù)據(jù)查找
注意:
- 查找時(shí)用循環(huán)遍歷鏈表
- 對(duì)于查找數(shù)據(jù)不用修改鏈表指針的內(nèi)容,故只需傳入鏈表指針就行了
- 查找到時(shí)則返回節(jié)點(diǎn)地址,否則返回NULL
代碼:
//鏈表數(shù)據(jù)查找
SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
//創(chuàng)建尋址指針
SLTNode* cur = phead;
while (cur!=NULL)
{
if (cur->data == x)//找到數(shù)據(jù)符合節(jié)點(diǎn)
{
return cur;//返回節(jié)點(diǎn)地址(好處:以便后續(xù)再尋找或修改)
}
else
{
//找下一個(gè)節(jié)點(diǎn)
cur = cur->next;
}
}
//未找到數(shù)據(jù)符合節(jié)點(diǎn)
return NULL;
}
鏈表pos位置前插數(shù)據(jù)
注意:
- 想要pos位置前插數(shù)據(jù),不僅需要找到pos位置,還需要記錄pos的前一個(gè)節(jié)點(diǎn)位置
- 傳入的pos為NULL則報(bào)錯(cuò)
- 進(jìn)行修改前節(jié)點(diǎn)的址域成新節(jié)點(diǎn),并讓新節(jié)點(diǎn)的址域修改成pos,這才是一個(gè)成功的pos位置前插數(shù)據(jù)
- 循環(huán)遍歷鏈表查找pos位置,沒有找到pos位置則什么也不干
代碼:
//鏈表pos位置往前插入(較難)(還有在pos位置之后插入,簡單點(diǎn))
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
//避免傳入錯(cuò)誤(直接報(bào)錯(cuò)便于找到錯(cuò)誤位置)
assert(pphead);
assert(pos);
SLTNode* newnode = BuySListNode(x);
//首結(jié)點(diǎn)符合的情況
if (*pphead == pos)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
//創(chuàng)建尋址指針
SLTNode* cur = *pphead;
while (cur !=NULL)
{
if (cur->next!= pos)
{
cur = cur->next;//找到下一節(jié)點(diǎn)
}
else // 找到對(duì)應(yīng)空間
{
cur->next = newnode;
newnode->next = pos;
return;//結(jié)束尋找(否則會(huì)一直插入,造成死循環(huán))
}
}
}
//未找到則什么也不干
return;
}
鏈表pos位置后插數(shù)據(jù)
注意:
- 后插則不用關(guān)注是否為首節(jié)點(diǎn)
- 也不用找到遍歷找到前節(jié)點(diǎn)的位置
- 后插則先將新節(jié)點(diǎn)址域改成pos后節(jié)點(diǎn)地址再將pos的址域改成新節(jié)點(diǎn)地址
ps:一定要注意修改鏈接節(jié)點(diǎn)址域的先后,避免地址的丟失
代碼:
//鏈表pos位置往后插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
return;
}
鏈表pos位置數(shù)據(jù)刪除
注意:
- 考慮刪除首節(jié)點(diǎn)的情況,可能修改鏈表指針的內(nèi)容,故需要傳入鏈表指針的地址
- 對(duì)于刪除節(jié)點(diǎn),需要先保存pos位置下一個(gè)節(jié)點(diǎn)地址,將pos位置釋放,再將pos位置前節(jié)點(diǎn)的址域改成pos位置后節(jié)點(diǎn)的地址,這才是成功的刪除pos位置節(jié)點(diǎn)
- 循環(huán)找pos位置,沒找到則什么也不干
參考代碼:
//鏈表pos位置刪除
void SListErase(SLTNode** pphead, SLTNode* pos)
{
//避免傳入錯(cuò)誤(直接報(bào)錯(cuò)便于找到錯(cuò)誤位置)
assert(pphead);
assert(pos);
//頭結(jié)點(diǎn)符合的情況
if (*pphead == pos)
{
*pphead = (*pphead)->next;
free(pos);
}
else
{
//創(chuàng)建尋址指針
SLTNode* cur = *pphead;
while (cur != NULL)
{
if (cur->next != pos)
{
cur = cur->next;//找到下一節(jié)點(diǎn)
}
else // 找到對(duì)應(yīng)空間
{
SLTNode* next = cur->next->next;
free(cur->next);
cur->next = next;
return;//結(jié)束尋找
}
}
}
//未找到則什么也不干
return;
}
鏈表節(jié)點(diǎn)釋放
注意:
- 對(duì)于動(dòng)態(tài)開辟的內(nèi)存空間,在使用后一定要記得的進(jìn)行釋放(避免造成內(nèi)存泄漏)
- 因?yàn)殒湵砉?jié)點(diǎn)是一個(gè)個(gè)開辟的,同樣的釋放也需要一個(gè)個(gè)進(jìn)行釋放
- 循環(huán)遍歷釋放當(dāng)前節(jié)點(diǎn)前需保存后一個(gè)節(jié)點(diǎn)的地址,避免地址丟失無法釋放
- 釋放完后,還需將鏈表指針給置空(避免使用野指針)
參考代碼:
//鏈表節(jié)點(diǎn)釋放
void SListDestory(SLTNode** pphead)
{
//避免傳入錯(cuò)誤(直接報(bào)錯(cuò)便于找到錯(cuò)誤位置)
assert(pphead);
//遍歷釋放
SLTNode* cur = *pphead;
while (cur)
{
//保存下一個(gè)地址
SLTNode* next = cur->next;
free(cur);
cur = next;
}
//置空
*pphead = NULL;
return;
}
鏈表與順序表區(qū)別
鏈表的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 按需申請(qǐng)空間(空間使用合理)
- 插入效率高(不用像順序表樣挪動(dòng)數(shù)據(jù))
缺點(diǎn)
- 不支持隨機(jī)訪問(無法用下標(biāo)直接訪問)
順序表的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 支持隨機(jī)訪問 (有些算法需要結(jié)構(gòu)支持隨機(jī)訪問:二分查找,快排等)
缺點(diǎn)
- 擴(kuò)容空間有消耗(空間碎片化以及空間浪費(fèi))
- 插入數(shù)據(jù)需要挪動(dòng)數(shù)據(jù)有消耗
到此這篇關(guān)于C++超詳細(xì)講解單鏈表的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++ 單鏈表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用C語言實(shí)現(xiàn)經(jīng)典多級(jí)時(shí)間輪定時(shí)器
C語言是一門通用計(jì)算機(jī)編程語言,廣泛應(yīng)用于底層開發(fā),這篇文章主要給大家介紹了關(guān)于利用C語言實(shí)現(xiàn)經(jīng)典多級(jí)時(shí)間輪定時(shí)器的相關(guān)資料,需要的朋友可以參考下2021-07-07
Matlab控制電腦攝像實(shí)現(xiàn)實(shí)時(shí)人臉檢測和識(shí)別詳解
人臉識(shí)別過程主要由四個(gè)階段組成:人臉檢測、圖像預(yù)處理、面部特征提取和特征識(shí)別。這篇文章主要介紹了如何使用MATLAB控制筆記本電腦的攝像頭,并進(jìn)行實(shí)時(shí)人臉檢測和識(shí)別,需要的可以參考一下2022-10-10
Qt數(shù)據(jù)庫應(yīng)用之實(shí)現(xiàn)數(shù)據(jù)圖文混排
除了能夠打印基本的文字信息數(shù)據(jù)到pdf和紙張,越來越多的應(yīng)用需求還要求能夠?qū)С鰣D片,并且要支持圖文混排。本文將通過Qt實(shí)現(xiàn)這一功能,需要的可以參考一下2022-01-01
淺談C++/C關(guān)于#define的那些奇奇怪怪的用法
本文主要介紹了C++/C關(guān)于#define的那些奇奇怪怪的用法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
C++動(dòng)態(tài)規(guī)劃實(shí)現(xiàn)查找最長公共子序列
這篇文章主要介紹了C++動(dòng)態(tài)規(guī)劃最長公共子序列,在動(dòng)態(tài)規(guī)劃中,你要將某個(gè)指標(biāo)最大化。在這個(gè)例子中,你要找出最長公共子序列2022-06-06
C語言編程中常見的五種錯(cuò)誤及對(duì)應(yīng)解決方案
這篇文章主要給大家分享的是C語言編程中常見的五種錯(cuò)誤及對(duì)應(yīng)解決方案,詳細(xì)內(nèi)容就請(qǐng)跟小編一起進(jìn)入下面的文章內(nèi)容吧2021-10-10
C++語言實(shí)現(xiàn)hash表詳解及實(shí)例代碼
這篇文章主要介紹了C++語言實(shí)現(xiàn)hash表詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01

