目錄
一. 單鏈表的實現(xiàn)
1.準(zhǔn)備工作及其注意事項
1.1 先創(chuàng)建三個文件
1.2 注意事項:幫助高效記憶和理解
2.鏈表的基本功能接口
2.0 創(chuàng)建一個 鏈表
2.1 鏈表的打印
?3.鏈表的創(chuàng)建新節(jié)點接口
4.鏈表的節(jié)點插入功能接口
4.1 尾插接口
4.2 頭插接口 ?
??4.3 指定位置 pos 之前?插入接口
?4.4 指定位置pos 之后 插入接口(推薦)
5.鏈表表的刪除功能接口
5.1 尾刪接口
5.2頭刪接口
5.3 刪除 指定位置 pos 節(jié)點 接口
5.4 刪除 ?指定位置 pos ==之后== 的一個 節(jié)點 接口
6.鏈表的 ?查找 ?接口
7.鏈表的 ?銷毀 ?接口
二、總代碼
SList.h
SList.c
test.c
前言:(受篇幅限制,為了劃清知識模塊,進(jìn)行分章節(jié)講解)
若想了解清楚 鏈表的概念和分類?:
可以點擊跳轉(zhuǎn) 這一章節(jié)----> :【數(shù)據(jù)結(jié)構(gòu)】鏈表的概念 及 分類 (使用比喻解釋概念)
一. 單鏈表的實現(xiàn)
1.準(zhǔn)備工作及其注意事項
1.1 先創(chuàng)建三個文件
?解釋這三個文件的作用
?1、頭文件SList.h ?是來聲明接口函數(shù),定義鏈表,將幾個公共用到的庫函數(shù)集合起來
?2、源文件SList.c ?是用來具體實現(xiàn)接口
?3、源文件test.c ?用于接口的測試工作 ,即具體的使用場景
1.2 注意事項:幫助高效記憶和理解
1. 但凡是刪除,必須 斷言 鏈表不能為 空,避免傳過來的是NULL指針 assert(*pphead);
2. 傳遞二級指針,要斷言 不能為 NULL ,指針不能為空:assert(pphead);
3. 指定位置 pos 時,要確保pos存在:assert(pos);
4. (pphead)->next; ?解引用前一定要加 括號,* 號 優(yōu)先級 < 箭頭操作符
5. 鏈表的所有插入接口:鏈表為空: 沒有節(jié)點 就直接插入
6.要找 前一個節(jié)點 prev 或 后一個節(jié)點 next 時, 一定要確保 其本身存在!?。?!
7.當(dāng)指定位置 pos 剛好是 頭節(jié)點時 就沒有 prev 了
2.鏈表的基本功能接口
2.0 創(chuàng)建一個 鏈表
// 創(chuàng)建鏈表節(jié)點結(jié)構(gòu)體
// 和順序表創(chuàng)建原理相同,可以看我的 【順序表】章節(jié)
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SLTNode;
2.1 鏈表的打印
// 打印函數(shù)
//注意這里的 phead 僅表示單向鏈表傳遞過來的 第一個節(jié)點地址,不是 雙向鏈表的帶頭節(jié)點
void SLTPrint(SLTNode* phead)
{
SLTNode* ptemp = phead;
while (ptemp) // ptemp != NULL 說明為是有效的節(jié)點,則循環(huán)打印
{
printf("%d -> ", ptemp->data);
ptemp = ptemp->next; // 更新 ptemp :保存下個節(jié)點的地址:遍歷節(jié)點的關(guān)鍵
}
printf("NULL\n");
}
?3.鏈表的創(chuàng)建新節(jié)點接口
// 創(chuàng)建新節(jié)點函數(shù):所有的插入接口,都需要 創(chuàng)建新節(jié)點
SLTNode* STLCreatNode(SLDataType x)
{
// 先創(chuàng)建 一個 新節(jié)點
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
newNode->data = x;
newNode->next = NULL;
return newNode;
}
4.鏈表的節(jié)點插入功能接口
4.1 尾插接口
鏈表的尾插法
幾種情況
1、鏈表非空:先找到尾節(jié)點,然后直接將尾節(jié)點 的 next 指向 新節(jié)點,(形成鏈接), 新節(jié)點 則 指向 NULL 變成 新的尾節(jié)點
2、鏈表為空:將頭指針phead更新 成 新插入節(jié)點的地址,然后新節(jié)點 next 指向 NULL
// 注意:plist 頭指針是 時刻更新為了指向頭節(jié)點,當(dāng)創(chuàng)建新的頭節(jié)點后,要更新 plist ,而若要真正改變 plist 的值,需要傳地址
// 傳遞過來的 頭指針 &plist 是 一級指針 plist 的地址,要用 二級指針 pphead 來接收 ; 而 *pphead == plist
// void SLTPushBack(SLTNode* phead, SLDataType x) // 錯誤寫法:傳值
void SLTPushBack(SLTNode** pphead, SLDataType x)
{
assert(pphead); // 傳遞過來的 一級指針的地址 二級指針 不能為 NULL
// 先創(chuàng)建 一個 新節(jié)點
SLTNode* newNode = STLCreatNode(x);
// 1、鏈表為空
if (*pphead == NULL)
{
*pphead = newNode;
return;
}
// 2、鏈表非空
SLTNode* ptail = *pphead; // 因為要找 尾節(jié)點:干脆命名為 tail
//本質(zhì)是:找到尾節(jié)點的地址,而不是 尾節(jié)點的 next
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newNode;
}
4.2 頭插接口 ?
void SLTPushFront(SLTNode** pphead, SLDataType x)
{
assert(pphead); // 傳遞過來的 一級指針的地址 二級指針 不能為 NULL
// 先創(chuàng)建 一個 新節(jié)點
SLTNode* newNode = STLCreatNode(x);
newNode->next = *pphead; // 先將原本的 頭指針放在 新節(jié)點的next
*pphead = newNode; // 更新 頭指針
}
??4.3 指定位置 pos 之前 插入接口
// 在指定位置節(jié)點pos 之前插入數(shù)據(jù)
// 思路:找三個節(jié)點 :prev newNode next
// 前 本身新節(jié)點 后 為了找到 地址 才能將三者鏈接起來
// 注意:要找 前一個節(jié)點 prev 時, 一定要確保 其本身存在!??!
// 當(dāng) pos 剛好是 頭節(jié)點時 就沒有 prev 了,特殊情況, 否則會因為找不到prev而 程序奔潰
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
assert(pphead); // 指針不能為空
assert(pos); // pos 不能為空
assert(*pphead); // 鏈表不能為空: pos 是鏈表中 的一個有效節(jié)點,pos 不為空,鏈表也不能為 空
// 創(chuàng)建新節(jié)點
SLTNode* newNode = STLCreatNode(x);
if (*pphead == pos)
{
// 當(dāng) pos 剛好是 頭節(jié)點時 就用 頭插法:調(diào)用之前寫的函數(shù)接口
SLTPushFront(pphead, x);
return;
}
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
// 三者關(guān)聯(lián)起來: prev -> newNode -> pos
newNode->next = pos;
prev->next = newNode;
}
?4.4 指定位置pos 之后 插入接口(推薦)
// 在指定位置節(jié)點pos 之后插入數(shù)據(jù): 注意一下錯誤寫法: 和正確寫法剛好相反
// 特點:有了 pos 就不需要 頭節(jié)點了??!
void SLTInsertAfter(SLTNode*pos, SLDataType x)
{
assert(pos); // pos 不能為空
// 創(chuàng)建新節(jié)點
SLTNode* newNode = STLCreatNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
5.鏈表表的刪除功能接口
5.1 尾刪接口
// 鏈表的尾刪法
// 思路:刪除尾節(jié)點 以及 前一個節(jié)點 pre 的next 要置為 NULL
// 要注意 只有一個節(jié)點時,沒有前置節(jié)點 pre?。。?!
void SLTPopBack(SLTNode** pphead)
{
assert(pphead); // 一級指針為plist 指向頭節(jié)點,傳遞過來的 一級指針的地址二級指針不能為 NULL
assert(*pphead); // 一級指針為plist 指向頭節(jié)點, *pphead == plist 這個指針也不能為 NULL 不能為空鏈表
// 鏈表非空
// 鏈表只有一個節(jié)點時
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
// 鏈表有多個節(jié)點
SLTNode* ptail = *pphead;
SLTNode* prev = NULL; // 時刻更新保存
while(ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
5.2頭刪接口
// 鏈表的頭刪法
void SLTPopFront(SLTNode** pphead)
{
assert(pphead); // 指針不能為空
assert(*pphead); // 鏈表不能為空
// 讓第二個節(jié)點成為新的頭節(jié)點
// 將 舊的節(jié)點釋放掉: 注意 要先將 第二個節(jié)點的地址 (*pphead) -> next 臨時儲存起來??!
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
5.3 刪除 指定位置 pos 節(jié)點 接口
// 刪除pos 節(jié)點
// 刪除 pos 節(jié)點后,還要 將 prev 和 next (pos的前后兩個節(jié)點) 鏈接上?。。?比較麻煩
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead); // 指針不能為空
assert(pos); // pos 不能為空
assert(*pphead); // 鏈表不能為空: pos 是鏈表中 的一個有效節(jié)點,pos 不為空,鏈表也不能為 空
//先 讓 prev 鏈接上 next,后 free 釋放掉 pos
// 遇到 需要找 prev 的,一定要 檢查是否存在 prev
if (*pphead == pos)//檢查是否存在 prev
{
free(pos);
*pphead = NULL;
return;
}
SLTNode* prev = *pphead;
while (prev->next != pos) // 找 前面節(jié)點 prev
{
prev = prev->next;
}
prev->next = pos->next; // 先賦值后 再free釋放
free(pos);
pos = NULL;
}
?5.4 刪除 ?指定位置 pos ==之后== 的一個 節(jié)點 接口
// 刪除pos 之后的節(jié)點
// 直接 刪除 pos 之后的 節(jié)點 pos->next
// 先讓 pos 和 pos -> next -> next 鏈接起來,后 刪除 節(jié)點 pos -> next
// 注意:像這類找 上一個節(jié)點 prev 或 找下一個節(jié)點 next :都需要 檢查 是否存在 該節(jié)點
void SLTEraseBeind(SLTNode* pos)
{
assert(pos); // pos 不能為空
assert(pos->next); // 這個節(jié)點必須存在,否則該函數(shù)無意義! 鏈表至少要有兩個節(jié)點
// 三者的關(guān)聯(lián)關(guān)系:pos pos->next pos->next->next
// 注意:下面 先 pos->next = pos->next->next, 后 free(pos->next) 是不對的
// pos->next 已經(jīng)更新,不是你所認(rèn)為的原來的那個 中間節(jié)點了,因此要先 臨時保存起來!??!
SLTNode* temp = pos->next;
pos->next = pos->next->next;
free(temp);
temp = NULL;
}
?6.鏈表的 ?查找 ?接口
// 鏈表的查找
SLTNode* SLTFind(SLTNode** pphead, SLDataType x)
{
// 遍歷鏈表
assert(pphead); // 指針不能為空
SLTNode* ptemp = *pphead;
while (ptemp)
{
if (ptemp->data == x) return ptemp; // 找到了就返回節(jié)點
ptemp = ptemp->next;
}
// 沒找到
return NULL;
}
7.鏈表的 ?銷毀 ?接口
// 銷毀鏈表
// 一旦涉及到 動態(tài)內(nèi)存申請,不要忘記銷毀
void SListDestory(SLTNode** pphead)
{
assert(pphead); // 指針不能為空
assert(*pphead); // 鏈表不能為空,空的沒必要銷毀了
// 順序表是一段連續(xù)的空間,可以執(zhí)行一次free全部銷毀,而鏈表節(jié)點是獨立的,需要遍歷節(jié)點一個一個銷毀
// 不能直接 一個一個 free 下去,需要 保存 next 節(jié)點,才能找到下個節(jié)點!?。?!
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
// 當(dāng)所有節(jié)點銷毀后,需要 將頭指針 銷毀
*pphead = NULL;
}
二、總代碼
SList.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
// 零、創(chuàng)建鏈表節(jié)點
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SLTNode;
// 一、打印函數(shù): 這里用傳址
void SLTPrint(SLTNode* ps);
// 二、尾插法
void SLTPushBack(SLTNode** pphead, SLDataType x);
// 三、頭插法
void SLTPushFront(SLTNode** pphead, SLDataType x);
// 四、尾刪法
void SLTPopBack(SLTNode** pphead);
// 五、頭刪法
void SLTPopFront(SLTNode** pphead);
// 六、鏈表的查找
SLTNode* SLTFind(SLTNode** pphead, SLDataType x);
// 七、指定位置 pos 之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);
// 八、指定位置 pos 之后 插入
void SLTInsertAfter(SLTNode* pos, SLDataType x);
// 九、刪除 指定 pos 節(jié)點
void SLTErase(SLTNode** pphead, SLTNode* pos);
// 十、刪除指定 pos 之后 的節(jié)點
void SLTEraseAfter(SLTNode* pos);
// 十一、銷毀鏈表
void SLTDestory(SLTNode** pphead);
SList.c
#include"SList.h"
// 一、打印函數(shù): 這里用傳址
void SLTPrint(SLTNode* phead)
{
SLTNode* ptemp = phead;
while (ptemp) // ptemp != NULL 表示為有效節(jié)點
{
printf("%d -> ", ptemp->data);
ptemp = ptemp->next;
}
printf("NULL\n");
}
// 創(chuàng)建節(jié)點函數(shù)
SLTNode* SLTCreatNode(SLDataType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
newNode->data = x;
newNode->next = NULL;
return newNode;
}
// 二、尾插法
void SLTPushBack(SLTNode** pphead, SLDataType x)
{
assert(pphead);
// 創(chuàng)建新節(jié)點
SLTNode* newNode = SLTCreatNode(x);
// 鏈表為空: 沒有節(jié)點 就直接插入
if (*pphead == NULL)
{
*pphead = newNode;
return;
}
// 鏈表非空:找尾節(jié)點
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newNode;
}
// 三、頭插法
void SLTPushFront(SLTNode** pphead, SLDataType x)
{
assert(pphead);
// 創(chuàng)建新節(jié)點
SLTNode* newNode = SLTCreatNode(x);
// 先 鏈接 第一個節(jié)點, 沒有第一個節(jié)點就是 NULL,也可以直接給 newNode->next,后更新 *pphead
newNode->next = *pphead;
*pphead = newNode;
}
// 四、尾刪法
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
// 先找尾節(jié)點 和 前置節(jié)點 prev,后刪除+鏈接
// 當(dāng)只有一個 節(jié)點時
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
// 當(dāng)有 兩個 節(jié)點 及以上
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
// 五、頭刪法
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
// 讓 *pphead 指向第二個節(jié)點,free 掉第一個
SLTNode* next = (*pphead)->next; // 一定要加 括號,* 號 優(yōu)先級 < 箭頭操作符
free(*pphead);
*pphead = next;
}
// 六、鏈表的查找
SLTNode* SLTFind(SLTNode** pphead, SLDataType x)
{
assert(pphead);
// 遍歷
SLTNode* ptemp = *pphead;
while (ptemp)
{
if (ptemp->data == x) return ptemp;
ptemp = ptemp->next;
}
return NULL;
}
// 七、指定位置 pos 之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
assert(pphead);
assert(pos);
assert(*pphead);
// 涉及到三個節(jié)點: prev newNode pos
// 創(chuàng)建新節(jié)點
SLTNode* newNode = SLTCreatNode(x);
if (*pphead == pos)// 無 prev 的情況
{
SLTPushFront(pphead, x);
return;
}
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newNode->next = pos;
prev->next = newNode;
}
// 八、指定位置 pos 之后 插入(推薦!)
void SLTInsertAfter(SLTNode* pos, SLDataType x)
{
assert(pos);
// 創(chuàng)建新節(jié)點
SLTNode* newNode = SLTCreatNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
// 九、刪除 指定 pos 節(jié)點
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
assert(*pphead);
// 關(guān)系三個節(jié)點:prev pos next
// 先找 prev
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
// 十、刪除指定 pos 之后 的節(jié)點
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next); // 注意 pos->next 這個必須存在
// 關(guān)系三個節(jié)點: pos pos->next pos->next->next
// 另一種可能: pos pos->next NULL
if (pos->next->next == NULL)
{
free(pos->next);
pos->next = NULL;
return;
}
// 直接銷毀 pos->next 會找不到 pos->next->next,先保存
SLTNode* ptemp = pos->next->next;
free(pos->next);
pos->next = NULL;
pos->next = ptemp;
}
// 十一、銷毀鏈表
void SLTDestory(SLTNode** pphead)
{
assert(pphead);
// 遍歷銷毀
if (*pphead == NULL)return;
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
// 別忘了將 頭指針銷毀?。?!
*pphead = NULL;
}
test.c
#include"SList.h"
void SLTest1()
{
// 零、創(chuàng)建 鏈表
SLTNode* plist = NULL;
// 二、尾插法
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4); // 1 -> 2 -> 3 -> 4 -> NULL
printf("測試尾插:");
SLTPrint(plist);
// 三、頭插法
SLTPushFront(&plist, 5);
SLTPushFront(&plist, 6); // 6 -> 5 -> 1 -> 2 -> 3 -> 4 -> NULL
printf("測試頭插:");
SLTPrint(plist);
// 四、尾刪法
SLTPopBack(&plist);
SLTPopBack(&plist); // 6 -> 5 -> 1 -> 2 -> NULL
printf("測試尾刪:");
SLTPrint(plist);
// 五、頭刪法
SLTPopFront(&plist);
SLTPopFront(&plist); // 1 -> 2 -> NULL
printf("測試頭刪:");
SLTPrint(plist);
// 六、鏈表的查找
SLTNode* FindRet = SLTFind(&plist, 2);
printf("測試查找:");
if (FindRet) printf("找到了\n");
else printf("沒找到\n");
// 七、指定位置 pos 之前插入
SLTNode* FindRet1 = SLTFind(&plist, 2);
SLTNode* FindRet2 = SLTFind(&plist, 1);
SLTInsert(&plist, FindRet1, 200);
SLTInsert(&plist, FindRet2, 100);// 100 -> 1 -> 200 -> 2 -> NULL
printf("測試指定位置之前插入:");
SLTPrint(plist);
// 八、指定位置 pos 之后 插入(推薦!)
SLTNode* FindRet3 = SLTFind(&plist, 2);
SLTInsertAfter(FindRet3, 200); // 100 -> 1 -> 200 -> 2 -> 200 -> NULL
printf("測試指定位置之后插入:");
SLTPrint(plist);
// 九、刪除 指定 pos 節(jié)點
SLTNode* FindRet4 = SLTFind(&plist, 1);
SLTErase(&plist, FindRet4);// 100 -> 200 -> 2 -> 200 -> NULL
printf("測試刪除指定節(jié)點:");
SLTPrint(plist);
// 十、刪除指定 pos 之后 的節(jié)點
SLTNode* FindRet5 = SLTFind(&plist, 200); // 注意如果 通過 Find 函數(shù) 找 節(jié)點,節(jié)點中有重復(fù)數(shù)據(jù),返回 第一個遇到的
SLTEraseAfter(FindRet5);// 100 -> 200 -> 200 -> NULL
printf("測試刪除指定節(jié)點:");
SLTPrint(plist);
//
}
int main()
{
SLTest1();
return 0;
}
完。文章來源:http://www.zghlxwxcb.cn/news/detail-825908.html
若上述文章有什么錯誤,歡迎各位大佬及時指出,我們共同進(jìn)步!文章來源地址http://www.zghlxwxcb.cn/news/detail-825908.html
到了這里,關(guān)于【數(shù)據(jù)結(jié)構(gòu)】單向鏈表實現(xiàn) 超詳細(xì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!