??前言
-
單鏈表是一種常見的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)一系列的數(shù)據(jù)元素,每個(gè)節(jié)點(diǎn)包含數(shù)據(jù)和指向下一個(gè)節(jié)點(diǎn)的指針。
-
單鏈表通常用于實(shí)現(xiàn)某些算法或數(shù)據(jù)結(jié)構(gòu),如鏈?zhǔn)角跋蛐?、哈希表、鏈?zhǔn)綏?、?duì)列等等。
-
單鏈表在程序設(shè)計(jì)中的作用不可忽略,是很多基礎(chǔ)算法的核心數(shù)據(jù)結(jié)構(gòu)之一。
-
學(xué)習(xí)單鏈表有助于提高算法和數(shù)據(jù)結(jié)構(gòu)的基本能力并增強(qiáng)編程的實(shí)踐經(jīng)驗(yàn)。
-
本篇博客將介紹單鏈表的基本操作及其算法應(yīng)用,旨在幫助讀者掌握單鏈表數(shù)據(jù)結(jié)構(gòu)及相關(guān)算法的設(shè)計(jì)和實(shí)現(xiàn),進(jìn)一步提高編程的能力和水平。
總之,單鏈表是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),與編程和算法設(shè)計(jì)密切相關(guān),了解和掌握單鏈表的基本概念和操作,對于提高編程能力和設(shè)計(jì)程序算法都有幫助。
??單鏈表與順序表的對比
單鏈表的優(yōu)點(diǎn):
- 動(dòng)態(tài)性:單鏈表可以在任意位置進(jìn)行插入和刪除操作,不必像順序表那樣需要移動(dòng)元素,操作比較靈活。
- 空間利用效率高:單鏈表的結(jié)構(gòu)只需要記錄一個(gè)指針域和一個(gè)數(shù)據(jù)域,不像順序表需要預(yù)留一定的存儲(chǔ)空間,因此空間利用率較高。
單鏈表的缺點(diǎn):
- 存儲(chǔ)空間分配不靈活:由于單鏈表的結(jié)構(gòu)決定了只有通過指針才能訪問相鄰元素,因此不能隨機(jī)訪問。
- 存儲(chǔ)密度較低:由于每個(gè)結(jié)點(diǎn)只包含一個(gè)指針域和一個(gè)數(shù)據(jù)域,因此存儲(chǔ)密度比較低,會(huì)占用更多的空間。
順序表的優(yōu)點(diǎn):
- 存儲(chǔ)密度較高:由于所有元素在連續(xù)的存儲(chǔ)空間中,因此存儲(chǔ)密度比較高,可以節(jié)省空間。
- 存取效率高:由于所有元素在連續(xù)的存儲(chǔ)空間中,因此可以通過元素的下標(biāo)進(jìn)行隨機(jī)存取,存取效率比較高。
順序表的缺點(diǎn):
- 插入和刪除操作較慢:由于需要移動(dòng)元素,因此插入和刪除操作比較慢。
- 維護(hù)困難:當(dāng)順序表達(dá)到一定的長度后,插入和刪除操作會(huì)變得更加困難,在需要頻繁進(jìn)行插入和刪除操作時(shí),順序表的效率會(huì)比較低。
總的來說,單鏈表和順序表各有優(yōu)點(diǎn)和缺點(diǎn),需要根據(jù)具體的應(yīng)用場景進(jìn)行選擇。如果需要頻繁進(jìn)行插入和刪除操作,單鏈表的效率更高;如果需要頻繁進(jìn)行隨機(jī)存取操作,順序表更為適合。
??單鏈表的初始操作及結(jié)構(gòu)體
- 單鏈表的實(shí)現(xiàn)也是需要搭建3個(gè)文件,一個(gè)是SList.h(頭文件),SList.c(各函數(shù)的實(shí)現(xiàn)文件),Test.c(測試文件)。
- 搭建好之后我們只需要在SList.c和Test.c文件開頭包含#include"SList.h"即可將三個(gè)文件鏈接起來。
- 寫單鏈表的結(jié)構(gòu)體時(shí),需要定義包含兩個(gè)成員的結(jié)構(gòu)體類型,一個(gè)成員用于存儲(chǔ)結(jié)點(diǎn)的數(shù)據(jù),另一個(gè)成員用于存儲(chǔ)下一個(gè)結(jié)點(diǎn)的指針。一般而言,結(jié)構(gòu)體類型的名稱和結(jié)構(gòu)體變量的名稱都需要有意義,以便于程序的閱讀和理解。
相關(guān)功能代碼:
#pragma once
// 輸入輸出所需頭文件
#include<stdio.h>
// malloc 所需頭文件
#include<stdlib.h>
// assert 所需頭文件
#include<assert.h>
// 每個(gè)節(jié)點(diǎn)的數(shù)據(jù)的類型
typedef int SLTDataType;
// 每個(gè)節(jié)點(diǎn)的結(jié)構(gòu)體
typedef struct SListNode
{
// 存放該結(jié)點(diǎn)的數(shù)據(jù)
SLTDataType data;
// 存放下一個(gè)節(jié)點(diǎn)的地址,將整個(gè)結(jié)構(gòu)串聯(lián)成一個(gè)單鏈表
struct SListNode* next;
}SLTNode; // 用 typedef 將結(jié)構(gòu)體重命名為 SLTNode 便于后續(xù)寫程序
??申請一個(gè)節(jié)點(diǎn)
-
單鏈表的節(jié)點(diǎn)是單鏈表中最基本的單位。每個(gè)節(jié)點(diǎn)由兩部分組成,分別是數(shù)據(jù)域和指針域。數(shù)據(jù)域存儲(chǔ)節(jié)點(diǎn)的數(shù)據(jù)或者信息,而指針域指向下一個(gè)節(jié)點(diǎn)的地址。因此,單鏈表是由一連串的節(jié)點(diǎn)構(gòu)成的數(shù)據(jù)結(jié)構(gòu)。具體來說,單鏈表的節(jié)點(diǎn)通常包含以下信息:
-
1.數(shù)據(jù)域:存儲(chǔ)節(jié)點(diǎn)的數(shù)據(jù)或信息,可以是任何數(shù)據(jù)類型,例如整數(shù)、字符、字符串、結(jié)構(gòu)體等。
-
2.指針域:指向下一個(gè)節(jié)點(diǎn)的地址。在C語言中可以使用結(jié)構(gòu)體來表示一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)結(jié)構(gòu)體至少包含一個(gè)數(shù)據(jù)項(xiàng)和一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針。
單鏈表的節(jié)點(diǎn)是非常靈活和可擴(kuò)展的,因?yàn)槊總€(gè)節(jié)點(diǎn)都只需要存儲(chǔ)自身的信息和指向下一個(gè)節(jié)點(diǎn)的指針就可以了,這就使得單鏈表更易于被實(shí)現(xiàn)和擴(kuò)展。當(dāng)需要插入或刪除節(jié)點(diǎn)時(shí),只需更新相應(yīng)節(jié)點(diǎn)的指針域即可。
同時(shí),這種設(shè)計(jì)也帶來了一定的缺點(diǎn),即單鏈表不能直接訪問任何一個(gè)節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)。
- 節(jié)點(diǎn)應(yīng)至少包括一個(gè)數(shù)據(jù)項(xiàng)(例如整數(shù)或字符串)和一個(gè)指向下一個(gè)節(jié)點(diǎn)的指針。
- 申請內(nèi)存要?jiǎng)?chuàng)建一個(gè)新的節(jié)點(diǎn),你需要使用C語言的stdlib.h庫中的malloc函數(shù)來為其分配內(nèi)存。將分配一個(gè)大小為SListNode結(jié)構(gòu)體的內(nèi)存塊,并將其指針指向newnode。在這個(gè)節(jié)點(diǎn)被使用完畢之后,應(yīng)該使用free函數(shù)釋放它的內(nèi)存。
- 經(jīng)過內(nèi)存分配后,你需要用正確的值初始化節(jié)點(diǎn)。在這種情況下,節(jié)點(diǎn)的值可以作為函數(shù)的參數(shù)進(jìn)行傳遞。初始化一個(gè)新的節(jié)點(diǎn)并返回這個(gè)節(jié)點(diǎn)的指針。
- 我們首先為節(jié)點(diǎn)分配內(nèi)存,然后將value賦值給節(jié)點(diǎn)的data字段,并把next設(shè)為NULL(因?yàn)檫@個(gè)節(jié)點(diǎn)尚未鏈接到另一個(gè)節(jié)點(diǎn)上)。
這樣,我們就成功地創(chuàng)建了一個(gè)新的節(jié)點(diǎn),并初始化了它的數(shù)據(jù)元素和指向下一個(gè)節(jié)點(diǎn)的指針。
SLTNode* BuyLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
??打印
- 單鏈表的打印操作是指遍歷單鏈表并輸出其中所有節(jié)點(diǎn)的數(shù)據(jù)或信息,這是單鏈表中常用的一種操作,也是單鏈表中尤為基礎(chǔ)的一部分。
- 定義一個(gè)指針變量,指向單鏈表的頭節(jié)點(diǎn),命名為cur。其中,head為單鏈表頭節(jié)點(diǎn)的指針。
- 使用while循環(huán)遍歷整個(gè)單鏈表,直到當(dāng)前的指針變量為空。
- 在循環(huán)中,使用p指針變量訪問當(dāng)前節(jié)點(diǎn)(即p所指向的節(jié)點(diǎn)),并打印其數(shù)據(jù)或信息。在實(shí)際操作中,可以根據(jù)需要定制輸出樣式。
- 將指針變量p更新為下一個(gè)節(jié)點(diǎn)的地址,即p = p->next,繼續(xù)訪問下一個(gè)節(jié)點(diǎn)。直到循環(huán)結(jié)束,所有的節(jié)點(diǎn)都被遍歷,并輸出了其中所有的數(shù)據(jù)或信息。
相關(guān)功能代碼:
void SLPrint(SLTNode* phead)
{
struct Node* cur = phead; // 定義指針變量,指向頭節(jié)點(diǎn)
while(cur != NULL){ // 循環(huán)條件
printf("%d ", cur->val); // 輸出節(jié)點(diǎn)數(shù)據(jù)或信息
cur = cur->next; // 修改指針變量,指向下一個(gè)節(jié)點(diǎn)
}
}
需要注意的是,單鏈表的打印操作在多種算法或數(shù)據(jù)結(jié)構(gòu)中都有應(yīng)用。
同時(shí),其中包含單個(gè)強(qiáng)迫離散節(jié)點(diǎn)間轉(zhuǎn)移到下一個(gè)節(jié)點(diǎn)的元素。其執(zhí)行步驟、方式及相關(guān)內(nèi)容會(huì)因場景而異,需要結(jié)合實(shí)際情況進(jìn)行設(shè)計(jì)。
??銷毀
- 單鏈表的銷毀是指將單鏈表中的所有節(jié)點(diǎn)都刪除,并將鏈表中的所有內(nèi)存空間釋放。
- 具體步驟:
- 1.定義一個(gè)指針變量pos來保存待刪除節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)。
- 2.使用while循環(huán)遍歷整個(gè)單鏈表,直到當(dāng)前指針plist為空。
- 3.在循環(huán)中,保存當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的指針,即pos = pos->next。
- 4.使用free函數(shù)釋放當(dāng)前節(jié)點(diǎn)的內(nèi)存空間,即free(head),釋放節(jié)點(diǎn)所占用的內(nèi)存空間。
- 5.將指針head指向下一個(gè)節(jié)點(diǎn)。即plist = pos。
重復(fù)步驟3-5,直到整個(gè)單鏈表中的所有節(jié)點(diǎn)都被刪除并釋放了內(nèi)存空間。最后在返回。 - 需要注意的是,有指針就別忘了要assert。
- 需要注意的是,在進(jìn)行單鏈表的尾插操作時(shí),需要保證單鏈表不為空??梢栽诤瘮?shù)外創(chuàng)建一個(gè)頭節(jié)點(diǎn),排除空鏈表情況,并將其傳入SLPushBack函數(shù)中進(jìn)行操作。另外,為新節(jié)點(diǎn)分配內(nèi)存空間之后,也需要對內(nèi)存申請情況進(jìn)行檢查,以避免發(fā)生內(nèi)存泄漏等問題。
相關(guān)程序代碼:
void SLDestory(SLTNode* plist,SLTNode* pos)
{
assert(plist);
while (pos)
{
plist = pos;
pos = pos->next;
free(plist);
plist->next = NULL;
}
return;
}
還需要注意的是,在使用上述函數(shù)時(shí),需要首先保證單鏈表中所有節(jié)點(diǎn)都已經(jīng)被訪問或使用,否則可能會(huì)造成內(nèi)存泄漏。
??尾插
- 單鏈表尾插是單鏈表中的一種常用操作,而且非常實(shí)用,它可以在單鏈表的末尾插入一個(gè)新節(jié)點(diǎn)。
- 找到單鏈表的尾部節(jié)點(diǎn),即包含指針域值為NULL的節(jié)點(diǎn)。可以用一個(gè)循環(huán)來找到它。
- 創(chuàng)建一個(gè)新節(jié)點(diǎn),將其數(shù)據(jù)放入其中。需要調(diào)用BuyLTNode函數(shù)為新節(jié)點(diǎn)分配內(nèi)存空間。
- 將新節(jié)點(diǎn)連接到單鏈表的尾部節(jié)點(diǎn)上,即將尾部節(jié)點(diǎn)的指針域指向新節(jié)點(diǎn)。至此,新的節(jié)點(diǎn)已經(jīng)成功插入到了單鏈表的末尾。
- 為新節(jié)點(diǎn)分配內(nèi)存空間之后,也需要對內(nèi)存申請情況進(jìn)行檢查(assert),以避免發(fā)生內(nèi)存泄漏等問題。
相關(guān)程序代碼:
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyLTNode(x);
assert(*pphead);
//1,空鏈表
//2,非空鏈表
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
??尾刪
- 單鏈表尾刪是指刪除單鏈表中的尾節(jié)點(diǎn),使其前一個(gè)節(jié)點(diǎn)成為新的尾節(jié)點(diǎn)。
- 找到單鏈表的倒數(shù)第二個(gè)節(jié)點(diǎn)??梢允褂靡粋€(gè)循環(huán)遍歷直到找到該節(jié)點(diǎn)。注意,需要判斷單鏈表中是否有節(jié)點(diǎn),否則刪除操作無法進(jìn)行
- 保存單鏈表尾節(jié)點(diǎn)的指針,以便后續(xù)釋放它所占用的內(nèi)存空間。
- 將尾節(jié)點(diǎn)從單鏈表中刪除,即將p節(jié)點(diǎn)的指針域設(shè)置為NULL。
- 釋放單鏈表尾節(jié)點(diǎn)占用的內(nèi)存空間。
相關(guān)程序代碼:
void SLPopBack(SLTNode** pphead)
{
//assert(pphead); //鏈表為空,pphead也不能為空,因?yàn)樗穷^指針plist的地址
assert(*pphead); //鏈表為空,不能頭刪
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
//找尾
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
需要注意的是,在進(jìn)行單鏈表的尾刪操作時(shí),需要先對單鏈表進(jìn)行判空處理,避免出現(xiàn)訪問空指針的錯(cuò)誤。
??頭插
- 單鏈表頭插是指在單鏈表的頭部插入一個(gè)新的節(jié)點(diǎn),使其成為新的頭節(jié)點(diǎn)。
- 創(chuàng)建一個(gè)新的節(jié)點(diǎn),并將新節(jié)點(diǎn)的指針域指向原來的頭節(jié)點(diǎn)。需要調(diào)用BuyLTNode函數(shù)為新節(jié)點(diǎn)分配內(nèi)存空間。
- 將頭指針指向新節(jié)點(diǎn),即將頭指針修改為新節(jié)點(diǎn)的地址。
相關(guān)程序代碼:
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
需要注意的是,在進(jìn)行單鏈表的頭插操作時(shí),需要先對頭指針進(jìn)行檢查,避免出現(xiàn)空指針的錯(cuò)誤。此外,在新建節(jié)點(diǎn)并為其分配內(nèi)存空間時(shí),也需要進(jìn)行內(nèi)存申請情況的檢查,以避免出現(xiàn)內(nèi)存泄漏等問題。
??頭刪
- 單鏈表頭刪是指刪除單鏈表中的頭節(jié)點(diǎn),使其后一個(gè)節(jié)點(diǎn)成為新的頭節(jié)點(diǎn)。
- 保存單鏈表頭節(jié)點(diǎn)的指針,以便后續(xù)釋放它所占用的內(nèi)存空間。
- 釋放原來的頭節(jié)點(diǎn)所占用的內(nèi)存空間。
相關(guān)程序代碼:
void SLPopFront(SLTNode** pphead)
{
//assert(pphead); //鏈表為空,pphead也不能為空,因?yàn)樗穷^指針plist的地址
assert(*pphead); //鏈表為空,不能頭刪。
SLTNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
}
需要注意的是,在進(jìn)行單鏈表的頭刪操作時(shí),需要先對頭指針進(jìn)行檢查,避免出現(xiàn)空指針的錯(cuò)誤。此外,在釋放原來的頭節(jié)點(diǎn)所占用的內(nèi)存空間時(shí),也需要進(jìn)行內(nèi)存申請情況的檢查,以避免出現(xiàn)內(nèi)存泄漏等問題。
??數(shù)據(jù)的查找
- 在單鏈表中查找數(shù)據(jù)通常需要遍歷整個(gè)鏈表,找到匹配的節(jié)點(diǎn)并返回其位置或指針。
- 使用一個(gè)指針指向鏈表的頭結(jié)點(diǎn),依次遍歷鏈表中的每一個(gè)節(jié)點(diǎn)。
- 在每個(gè)節(jié)點(diǎn)中,檢查該節(jié)點(diǎn)中存儲(chǔ)的元素是否與待查找元素相同。如果相同,則返回該節(jié)點(diǎn)的位置或指針。
- 如果遍歷整個(gè)鏈表都找不到相匹配的節(jié)點(diǎn),則返回NULL表示查找失敗。
相關(guān)程序代碼:
SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
需要注意的是,在進(jìn)行單鏈表中數(shù)據(jù)查找操作時(shí),需要先對單鏈表進(jìn)行判空處理,避免出現(xiàn)訪問空指針的錯(cuò)誤。
此外,還需注意的是,本算法只能查找鏈表中第一個(gè)匹配的元素,無法查找所有匹配的元素。
??數(shù)據(jù)的修改
- 在單鏈表中修改指定節(jié)點(diǎn)的數(shù)據(jù),通常需要先通過遍歷找到該節(jié)點(diǎn),之后直接修改其存儲(chǔ)的數(shù)據(jù)。
- 使用一個(gè)指針指向鏈表的頭結(jié)點(diǎn),依次遍歷鏈表中的每一個(gè)節(jié)點(diǎn)。
- 在每個(gè)節(jié)點(diǎn)中,檢查該節(jié)點(diǎn)中存儲(chǔ)的元素是否與待修改元素相同。如果相同,則直接修改該節(jié)點(diǎn)中存儲(chǔ)的數(shù)據(jù)。
- 如果遍歷整個(gè)鏈表都找不到匹配的節(jié)點(diǎn),則返回NULL表示操作失敗。
相關(guān)程序代碼:
SLTNode* SLModify(SLTNode* phead, int target, int newdata)
{
SLTNode* cur = phead;
while (cur != NULL) {
if (cur->data == target) {
cur->data = newdata;
return cur;
}
cur = cur->next;
}
return NULL;
}
需要注意的是,在進(jìn)行單鏈表中數(shù)據(jù)修改操作時(shí),需要先對單鏈表進(jìn)行判空處理,避免出現(xiàn)訪問空指針的錯(cuò)誤。此外,本算法只能修改鏈表中第一個(gè)匹配的元素,無法修改所有匹配的元素。
??在pos位置之后插入節(jié)點(diǎn)
- 首先對pos進(jìn)行判空操作。
- 創(chuàng)建一個(gè)新節(jié)點(diǎn) newnode,調(diào)用BuyLTNode函數(shù)。
- 用newnode的next指向pos位置的next,在用pos的next指向newnode。
相關(guān)程序代碼:
//在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
??在pos位置之前插入節(jié)點(diǎn)
- 在pos位置之前插入,因?yàn)閱捂湵砭哂袉蜗蛐?,因此這里需要遍歷單鏈表找到pos位置的前一個(gè)節(jié)點(diǎn),才能進(jìn)行插入。
- 但是你會(huì)發(fā)現(xiàn),如果是pos剛好指向頭結(jié)點(diǎn)就可以直接用頭插。
- 因?yàn)檫@里調(diào)用了頭插,所以這里也要使用二級(jí)指針來操作。
相關(guān)程序代碼:
//在pos之前插入
void SLInsertFront(SLTNode** pphead,SLTNode* pos,SLTDataType x)
{
assert(pphead);
assert(pos);
//如果pos為第一個(gè)節(jié)點(diǎn),直接調(diào)用前面的頭插
if (*pphead == pos)
{
SLPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
??刪除pos位置之后的節(jié)點(diǎn)
- 找到要?jiǎng)h除節(jié)點(diǎn)的位置。
- 將要?jiǎng)h除節(jié)點(diǎn)的 next 指向NULL。
- 找到前一個(gè)節(jié)點(diǎn),將其next指向NULL。
- 釋放要?jiǎng)h除節(jié)點(diǎn)及其后繼節(jié)點(diǎn)內(nèi)存空間。
相關(guān)程序代碼:
void SLEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
??刪除pos位置之前的節(jié)點(diǎn)
- 首先斷言(assert),然后再調(diào)用頭刪(SLPopFront).
- 之后再將 prev 的 next 指向要?jiǎng)h除節(jié)點(diǎn)的next。
- 釋放要?jiǎng)h除節(jié)點(diǎn)的內(nèi)存空間。
- 釋放要?jiǎng)h除及其前面的節(jié)點(diǎn)。
相關(guān)程序代碼:
void SLEraseFront(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
??完整代碼
SList.h
#pragma once
// 輸入輸出所需頭文件
#include<stdio.h>
// malloc 所需頭文件
#include<stdlib.h>
// assert 所需頭文件
#include<assert.h>
// 每個(gè)節(jié)點(diǎn)的數(shù)據(jù)的類型
typedef int SLTDataType;
// 每個(gè)節(jié)點(diǎn)的結(jié)構(gòu)體
typedef struct SListNode
{
// 存放該結(jié)點(diǎn)的數(shù)據(jù)
SLTDataType data;
// 存放下一個(gè)節(jié)點(diǎn)的地址,將整個(gè)結(jié)構(gòu)串聯(lián)成一個(gè)單鏈表
struct SListNode* next;
}SLTNode; // 用 typedef 將結(jié)構(gòu)體重命名為 SLTNode 便于后續(xù)寫程序
//申請一個(gè)節(jié)點(diǎn)(包含初始化)
SLTNode* BuyLTNode(SLTDataType x);
//單鏈表的打印
void SLTPrint(SLTNode* phead);
//頭插節(jié)點(diǎn)
void SLPushFront(SLTNode** pphead, SLTDataType x);
//尾插節(jié)點(diǎn)
void SLPushBack(SLTNode** pphead, SLTDataType x);
//頭刪節(jié)點(diǎn)
void SLPopFront(SLTNode** pphead);
//尾刪節(jié)點(diǎn)
void SLPopBack(SLTNode** pphead);
//節(jié)點(diǎn)的查找
SLTNode* SLFind(SLTNode* phead, SLTDataType x);
//節(jié)點(diǎn)的修改
SLTNode* SLModify(SLTNode* phead, int target, int newdata);
//pos位置之前插入節(jié)點(diǎn)
void SLInsertFront(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//pos位置之后插入節(jié)點(diǎn)
void SLInsertAfter(SLTNode* pos, SLTDataType x);
//pos位置之前刪除節(jié)點(diǎn)
void SLEraseFront(SLTNode** pphead, SLTNode* pos);
//pos位置之后刪除節(jié)點(diǎn)
void SLEraseAfter(SLTNode* pos);
//節(jié)點(diǎn)的銷毀
void SLDestory(SLTNode* plist, SLTNode* pos);
SList.c
#include"SList.h"
//單鏈表的打印
void SLPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
//申請一個(gè)節(jié)點(diǎn)(包含初始化)
SLTNode* BuyLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//頭插節(jié)點(diǎn)
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//尾插節(jié)點(diǎn)
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyLTNode(x);
assert(*pphead);
//1,空鏈表
//2,非空鏈表
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
//頭刪節(jié)點(diǎn)
void SLPopFront(SLTNode** pphead)
{
//assert(pphead); //鏈表為空,pphead也不能為空,因?yàn)樗穷^指針plist的地址
assert(*pphead); //鏈表為空,不能頭刪。
SLTNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
}
//尾刪節(jié)點(diǎn)
void SLPopBack(SLTNode** pphead)
{
//assert(pphead); //鏈表為空,pphead也不能為空,因?yàn)樗穷^指針plist的地址
assert(*pphead); //鏈表為空,不能頭刪
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
//找尾
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
//查找單鏈表的節(jié)點(diǎn)
SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//修改單鏈表的節(jié)點(diǎn)
SLTNode* SLModify(SLTNode* phead, int target, int newdata)
{
SLTNode* cur = phead;
while (cur != NULL) {
if (cur->data == target) {
cur->data = newdata;
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos之前插入節(jié)點(diǎn)
void SLInsertFront(SLTNode** pphead,SLTNode* pos,SLTDataType x)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SLPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
//在pos之后插入節(jié)點(diǎn)
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//刪除pos位置之前的節(jié)點(diǎn)
void SLEraseFront(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
//刪除pos位置之后的節(jié)點(diǎn)
void SLEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
//單鏈表的銷毀
void SLDestory(SLTNode* plist,SLTNode* pos)
{
assert(plist);
while (pos)
{
plist = pos;
pos = pos->next;
free(plist);
plist->next = NULL;
}
return;
}
Test,c
#include"SList.h"
void TestSList1()
{
SLTNode* plist = NULL;
SLPushFront(&plist, 1);
SLPushFront(&plist, 2);
SLPushFront(&plist, 3);
SLPushFront(&plist, 4);
SLPushFront(&plist, 5);
SLPrint(plist);
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPrint(plist);
SLPushBack(&plist, 5);
SLPrint(plist);
SLPopBack(&plist, 1);
SLPopBack(&plist, 2);
SLPopBack(&plist, 3);
SLPopBack(&plist, 4);
SLPopBack(&plist, 5);
SLPrint(plist);
SLPopFront(&plist, 1);
SLPopFront(&plist, 2);
SLPopFront(&plist, 3);
SLPopFront(&plist, 4);
SLPrint(plist);
}
void TestSList2()
{
SLTNode* plist = NULL;
SLPushFront(&plist, 1);
SLPushFront(&plist, 2);
SLPushFront(&plist, 3);
SLPushFront(&plist, 4);
SLPushFront(&plist, 5);
SLPushFront(&plist, 6);
SLPushFront(&plist, 7);
SLPrint(plist);
SLModify(plist, 1, 999);
SLPrint(plist);
SLModify(plist, 2, 999);
SLPrint(plist);
SLModify(plist, 3, 999);
SLPrint(plist);
SLModify(plist, 4, 999);
SLPrint(plist);
SLModify(plist, 5, 999);
SLPrint(plist);
SLModify(plist, 6, 999);
SLPrint(plist);
SLModify(plist, 7, 999);
SLPrint(plist);
}
void TestSList3()
{
SLTNode* plist = NULL;
SLPushFront(&plist, 1);
SLPushFront(&plist, 2);
SLPushFront(&plist, 3);
SLPushFront(&plist, 4);
SLPushFront(&plist, 5);
SLPushFront(&plist, 6);
SLPushFront(&plist, 7);
SLPrint(plist);
SLFind(&plist, 3);
SLPrint(plist);
}
int main()
{
//TestSList1();
//TestSList2();
TestSList3();
return 0;
}
??寫在最后
單鏈表是一種與數(shù)組相對的數(shù)據(jù)結(jié)構(gòu),在實(shí)際的開發(fā)中很常見。它雖然不能像數(shù)組那樣隨機(jī)訪問,但它具有動(dòng)態(tài)的特點(diǎn),可以高效地進(jìn)行插入和刪除操作。了解單鏈表的實(shí)現(xiàn)方式和使用方法,有助于我們在實(shí)際開發(fā)中更加靈活地運(yùn)用它。
在本篇博客中,我們介紹了單鏈表的基本知識(shí),包括如何在單鏈表中插入節(jié)點(diǎn)和刪除節(jié)點(diǎn)。
需要注意的是,在單鏈表操作的過程中,對指針的操作十分關(guān)鍵,需要謹(jǐn)慎地操作。文章來源:http://www.zghlxwxcb.cn/news/detail-466186.html
感謝各位閱讀本小白的博客,希望能幫助到大家!也請大家嚴(yán)厲指出并糾正我在文章中的錯(cuò)誤。??文章來源地址http://www.zghlxwxcb.cn/news/detail-466186.html
到了這里,關(guān)于數(shù)據(jù)結(jié)構(gòu)——實(shí)現(xiàn)單向鏈表的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!