【數據結構】 鏈表 + 手動實現單鏈表和雙鏈表的接口(圖文并茂附完整源碼)

文章目錄

一、 鏈表的概念及結構

二、鏈表的分類

?編輯

三、手動實現單鏈表

1、定義單鏈表的一個節點

2、打印單鏈表

3、創建新節點

4、單鏈表的尾插

5、單鏈表的頭插

6、單鏈表的尾刪

7、單鏈表的頭刪

8、單鏈表的查找

?9、在指定位置之前插入一個新節點

10、在指定位置之后插入一個新節點

11、刪除指定節點

12、刪除指定節點之后的節點

13、銷毀鏈表

四、單鏈表實現完整代碼

五、手動實現雙向鏈表

1、定義雙向鏈表的一個節點

2、創建新節點

3、雙向鏈表的初始化

4、打印

5、尾插

6、頭插

7、尾刪

8、頭刪

9、查找

10、指定位置之后插入

11、刪除指定位置的節點

12、銷毀

?六、雙向鏈表完整源碼


一、 鏈表的概念及結構

鏈表是?種物理存儲結構上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。即鏈表的邏輯結構是線性的物理結構是非線性的

如上圖所示,鏈表的每個節點在內存中都是隨機分布著的,所以其物理結構(存儲結構)為非連續,非順序的;但是,鏈表的每個節點中的指針指向下一個節點,所以其邏輯結構是線性的。?

?如圖,鏈表的結構跟火車車廂相似,淡季時車次的車廂會相應減少,旺季時車次的車廂會額外增加幾節。只需要將火車里的某節車廂去掉/加上,不會影響其他車廂,每節車廂都是獨立存在的。 車廂是獨立存在的,且每節車廂都有車門。想象一下這樣的場景,假設每節車廂的車門都是鎖上的狀態,需要不同的鑰匙才能解鎖,每次只能攜帶一把鑰匙的情況下如何從車頭走到車尾? 最簡單的做法:每節車廂里都放一把下一節車廂的鑰匙。

再對應到鏈表,火車的車廂就是鏈表的節點,而下一節車廂中放的鑰匙就是鏈表的每個節點中存放的下一個節點的地址。

鏈表的每個類似于車廂的結構,都是獨立申請的空間,我們稱之為節點。每個節點都由兩部分組成:想要存儲的數據存儲下一個節點地址的指針。鏈表中每個節點都是獨立申請的(即需要插?數據時才去申請?塊節點的空間),我們需要通過指針 變量來保存下?個節點位置才能從當前節點找到下?個節點。

二、鏈表的分類

鏈表的結構非常多樣,以下情況組合起來就有8種(2x2x2)鏈表結構:

?1、帶頭與不帶頭

帶頭即鏈表有頭節點(哨兵位),頭節點不存放實際有意義的數據,只是用來占位置。有哨兵位的鏈表就是帶頭鏈表,如果帶頭鏈表只有一個頭節點,該鏈表就為空鏈表。反之,沒有哨兵位就是不帶頭鏈表,這時,鏈表的第一個節點就不能稱為頭節點。

2、單向與雙向?

單向即鏈表的節點只有一個指向下一個節點的next指針。

雙向即鏈表的節點不僅有一個指向下一個節點的next指針,還有指向上一個節點的prev指針。

3、循環與不循環

循環鏈表的尾節點的指針并不指向NULL,而是指向第一個有效的節點?。

三、手動實現單鏈表

根據上面鏈表的分類,單鏈表為不帶頭單向不循環鏈表。

為了方便閱讀和查閱,我們先將單鏈表的頭文件代碼展示出來

SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int DateType;
//創建單鏈表的一個節點
typedef struct SList
{DateType val;struct SList* next;//指向下一個節點的指針
}SLT;//打印單鏈表
void PrintSLT(SLT* phead);
//鏈表尾插
void SLTpushBack(SLT** pphead, DateType x);
//鏈表頭插
void SLTpushFront(SLT** pphead, DateType x);
//鏈表尾刪
void SLTpopBack(SLT** pphead);
//鏈表頭刪
void SLTpopFront(SLT** pphead);
//鏈表的查找
SLT* SLTFind(SLT* ps, DateType x);
//在鏈表的指定位置之前插入一個新的節點
void SLTInsert(SLT** pphead, SLT* pos, DateType x);
//在鏈表的指定位置之后插入一個新的節點
void SLTInsertAfter(SLT* pos, DateType x);
//刪除指定節點
void SLTErase(SLT** pphead, SLT* pos);
//刪除指定節點之后的節點
void SLTEraseAfter(SLT* pos);
//銷毀鏈表中的所有節點
void SLTDestry(SLT** pphead);

1、定義單鏈表的一個節點

typedef int DateType;
//創建單鏈表的一個節點
typedef struct SList
{DateType x;struct SList* next;//指向下一個節點的指針
}SLT;

節點中有兩個變量,一個用來存儲數據,一個用來存儲下一個節點的地址。由于next指針指向下一個節點,所以next為struct SList* 類型的指針變量。用typedef關鍵字來重定義,既方便我們修改,也方便我們書寫。

2、打印單鏈表

想要打印單鏈表,只需要將單鏈表遍歷一次。但是有個小細節,我們需要創建另一個SLT*類型的指針pcur,然后用這個指針來遍歷單鏈表,防止指針指向改變而找不到鏈表的第一個節點。

//打印單鏈表
void PrintSLT(SLT* phead)
{SLT* pcur = phead;while (pcur){printf("%d->", pcur->val);pcur = pcur->next;}
}

3、創建新節點

想要實現單鏈表的尾插,頭插以及指定位置插入數據,就避免不了要創建新的節點。由于鏈表再存儲結構上非連續,所以我們用malloc來動態申請內存空間,然后將新節點的地址返回。

//創建新節點
SLT* BuyNode(DateType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));if (newnode == NULL){perror("malloc");exit(1);}newnode->val = x;newnode->next = NULL;return newnode;
}

4、單鏈表的尾插

(1)對pphead斷言操作,因為我們不能對空指針進行解引用操作。但是,*pphead可以為空,此時鏈表為空鏈表。

?(2)創建一個新節點newnode,用來存放指定的數據,并返回新節點的地址。

(3)鏈表是否為空:

空鏈表:即*pphead=NULL,我們只需要讓*pphead=newnode;

非空鏈表:因為要尾插,我們先要找到鏈表的尾節點,注意,while循環結束的條件是:

pcur->next = NULL。然后讓尾節點的next指針指向新節點。

注意:當我們在尾插時,如果直接將plist作為實參傳遞給形參phead,那么我們發現,形參phead并不會影響實參plist的值。我們想要改變實參plist的值,就要傳地址,而不是傳值。由于plist是SLT*類型的指針,所以需要用SLT**類型的二級指針來接收實參。

如下圖為傳值調用,經過調試和運行,plist中的val值并沒有發生改變。

//鏈表尾插
void SLTpushBack(SLT** pphead, DateType x)
{//斷言pphead不能為NULL,不能對空指針解引用assert(pphead);//創建新節點SLT* newnode = BuyNode(x);//處理空鏈表if (*pphead == NULL){*pphead = newnode;}//處理非空鏈表else{SLT* pcur = *pphead;while (pcur->next)//找尾節點{pcur = pcur->next;}pcur->next = newnode;}
}

5、單鏈表的頭插

首先同樣是對pphead斷言操作,然后創建新節點,讓新節點的next指針指向原來的第一個節點,這時候我們就不需要關注;吧是否為空了。

//鏈表頭插
void SLTpushFront(SLT** pphead, DateType x)
{assert(pphead);SLT* newnode = BuyNode(x);SLT* pcur = *pphead;newnode->next = pcur;*pphead = newnode;
}

6、單鏈表的尾刪

由于鏈表的節點的空間都是malloc動態申請的,因此,刪除鏈表的節點即釋放節點所占的空間。

(1)對pphead斷言;且對*pphead斷言,因為,如果*pphead = NULL,則鏈表為空鏈表,不需要尾刪。

(2)處理不同節點數量的情況:

只有一個節點:當 *pphead->next = NULL時,即只有一個節點,直接將*pphead釋放。

不止一個節點:我們需要先找到鏈表的尾節點(遍歷),然后釋放。然后將尾節點的前一個節點的next指針置為空。因此,我們需要用創建pcur指針,防止找不到第一個節點;同時,創建prev指針記錄尾節點的前一個節點。

//鏈表尾刪
void SLTpopBack(SLT** pphead)
{//斷言assert(pphead && *pphead);//只有一個節點if ((*pphead)->next == NULL){free(*pphead);//釋放*pphead = NULL;}//不止一個節點else{SLT* pcur = *pphead;SLT* prev = NULL;while (pcur->next){prev = pcur;pcur = pcur->next;}free(pcur);//釋放pcur = NULL;prev->next = NULL;}
}

7、單鏈表的頭刪

(1)對pphead斷言;且對*pphead斷言,因為,如果*pphead = NULL,則鏈表為空鏈表,不需要尾刪。

(2)釋放第一個節點。但在釋放之前需要用一個指針pcur記下第一個節點之后的節點,因為釋放后,*pphead應該為原來的第二個節點。

//鏈表頭刪
void SLTpopFront(SLT** pphead)
{//斷言assert(pphead && *pphead);SLT* pcur = (*pphead)->next;free(*pphead);*pphead = pcur;}

8、單鏈表的查找

先斷言,phead不能為空鏈表;然后創建pcur指針遍歷鏈表,防止改變phead指針指向而找不到第一個節點。

//鏈表的查找
SLT* SLTFind(SLT* phead, DateType x)
{assert(phead);SLT* pcur = phead;while (pcur){if (pcur -> val == x){return pcur;}pcur = pcur->next;}return NULL;
}

?9、在指定位置之前插入一個新節點

(1)斷言;

(2)創建新節點;

(3)將新節點與鏈表連接起來。我們需要找到指定位置之前的節點prev,然后將prev節點,新節點newnode和指定位置的節點pos連接起來。

//在鏈表的指定位置之前插入一個新的節點
void SLTInsert(SLT** pphead, SLT* pos, DateType x)
{assert(pphead && *pphead);assert(pos);SLT* newnode = BuyNode(x);//找pos節點之前的節點SLT* prev = *pphead;while (prev->next != pos){prev = prev->next;}//連接prev,newnode,posprev->next = newnode;newnode->next = pos;
}

10、在指定位置之后插入一個新節點

(1)斷言;

(2)創建新節點;

(3)將新節點與鏈表連接起來。我們需要找到指定位置之后的節點pback,然后將指定位置的節點、新節點newnode和pback節點連接起來。

//在鏈表的指定位置之后插入一個新的節點
void SLTInsertAfter(SLT* pos, DateType x)
{assert(pos);SLT* newnode = BuyNode(x);SLT* pback = pos->next;//連接pos,newnode,pbackpos->next = newnode;newnode->next = pback;
}

11、刪除指定節點

(1)斷言;

(2)pos為第一個節點:釋放后首節點改變。

pos不是第一個節點:釋放指定節點,但在此之前需要先將指定節點之前的節點prev和指定節點之后的節點pback記錄下來,最后連接prev節點和pback節點。

//刪除指定節點
void SLTErase(SLT** pphead, SLT* pos)
{assert(pphead && *pphead);assert(pos);//指定節點為第一個節點if (*pphead == pos){SLT* pcur = (*pphead)->next;free(*pphead);*pphead = pcur;}else{SLT* prev = *pphead;while (prev->next != pos){prev = prev->next;}//先連接pos節點的前一個節點與pos的后一個節點prev->next = pos->next;free(pos);pos = NULL;}
}

12、刪除指定節點之后的節點

先斷言,看pos是否為空且pos之后的節點是否為尾節點;若斷言通過,則創建std記錄pos之后的節點,以及創建later 記錄std的后一個節點,然后連接pos與later;最后釋放指定節點之后的節點。


//刪除指定節點之后的節點
void SLTEraseAfter(SLT* pos)
{assert(pos&&pos->next);//pos->next:處理當pos是最后一個節點的情況SLT* std = pos->next;SLT* later = std->next;free(std);std = NULL;pos->next = later;
}

13、銷毀鏈表

斷言,是否傳了空指針,鏈表是否為空;然后遍歷鏈表,逐個釋放。

//銷毀鏈表中的所有節點
void SLTDestry(SLT** pphead)
{assert(pphead && *pphead);SLT* std = *pphead;while (std){//記錄要銷毀節點的下一個節點,防止釋放而找不到SLT* next = std->next;free(std);std = next;}*pphead = NULL;
}

四、單鏈表實現完整代碼

SList.c

#include"SList.h"//打印單鏈表
void PrintSLT(SLT* phead)
{SLT* pcur = phead;while (pcur){printf("%d->", pcur->val);pcur = pcur->next;}
}//創建新節點
SLT* BuyNode(DateType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));if (newnode == NULL){perror("malloc");exit(1);}newnode->val = x;newnode->next = NULL;return newnode;
}//鏈表尾插
void SLTpushBack(SLT** pphead, DateType x)
{//斷言pphead不能為NULL,不能對空指針解引用assert(pphead);//創建新節點SLT* newnode = BuyNode(x);//處理空鏈表if (*pphead == NULL){*pphead = newnode;}//處理非空鏈表else{SLT* pcur = *pphead;while (pcur->next)//找尾節點{pcur = pcur->next;}pcur->next = newnode;}
}//鏈表頭插
void SLTpushFront(SLT** pphead, DateType x)
{assert(pphead);SLT* newnode = BuyNode(x);SLT* pcur = *pphead;newnode->next = pcur;*pphead = newnode;
}//鏈表尾刪
void SLTpopBack(SLT** pphead)
{//斷言assert(pphead && *pphead);//只有一個節點if ((*pphead)->next == NULL){free(*pphead);//釋放*pphead = NULL;}//不止一個節點else{SLT* pcur = *pphead;SLT* prev = NULL;while (pcur->next){prev = pcur;pcur = pcur->next;}free(pcur);//釋放pcur = NULL;prev->next = NULL;}
}//鏈表頭刪
void SLTpopFront(SLT** pphead)
{//斷言assert(pphead && *pphead);SLT* pcur = (*pphead)->next;free(*pphead);*pphead = pcur;}//鏈表的查找
SLT* SLTFind(SLT* phead, DateType x)
{assert(phead);SLT* pcur = phead;while (pcur){if (pcur -> val == x){return pcur;}pcur = pcur->next;}return NULL;
}//在鏈表的指定位置之前插入一個新的節點
void SLTInsert(SLT** pphead, SLT* pos, DateType x)
{assert(pphead && *pphead);assert(pos);SLT* newnode = BuyNode(x);//找pos節點之前的節點SLT* prev = *pphead;while (prev->next != pos){prev = prev->next;}//連接prev,newnode,posprev->next = newnode;newnode->next = pos;
}
//在鏈表的指定位置之后插入一個新的節點
void SLTInsertAfter(SLT* pos, DateType x)
{assert(pos);SLT* newnode = BuyNode(x);SLT* pback = pos->next;//連接pos,newnode,pbackpos->next = newnode;newnode->next = pback;
}//刪除指定節點
void SLTErase(SLT** pphead, SLT* pos)
{assert(pphead && *pphead);assert(pos);//指定節點為第一個節點if (*pphead == pos){SLT* pcur = (*pphead)->next;free(*pphead);*pphead = pcur;}else{SLT* prev = *pphead;while (prev->next != pos){prev = prev->next;}//先連接pos節點的前一個節點與pos的后一個節點prev->next = pos->next;free(pos);pos = NULL;}
}//刪除指定節點之后的節點
void SLTEraseAfter(SLT* pos)
{assert(pos && pos->next);//pos->next:處理當pos是最后一個節點的情況SLT* std = pos->next;SLT* later = std->next;free(std);std = NULL;pos->next = later;
}//銷毀鏈表中的所有節點
void SLTDestry(SLT** pphead)
{assert(pphead && *pphead);SLT* std = *pphead;while (std){//記錄要銷毀節點的下一個節點,防止釋放而找不到SLT* next = std->next;free(std);std = next;}*pphead = NULL;
}

test.c?

#include"SList.h"void test01()
{SLT* s1 = (SLT*)malloc(sizeof(SLT));SLT* s2 = (SLT*)malloc(sizeof(SLT));SLT* s3 = (SLT*)malloc(sizeof(SLT));s1->val = 1;s2->val = 2;s3->val = 3;s1->next = s2;s2->next = s3;s3->next = NULL;PrintSLT(s1);//打印//SLTpopBack(&s1);//尾刪//SLTpopFront(&s1);//頭刪//SLTpopFront(&s1);//頭刪printf("\n");//PrintSLT(s1);SLT*ret = SLTFind(s1, 2);//查找//printf("%d\n", ret->val);//SLTInsert(&s1, ret, 4);//指定位置之前插入//PrintSLT(s1);//SLTInsertAfter(ret, 4);//指定位置之后插入//PrintSLT(s1);//刪除指定節點SLTErase(&s1, ret);PrintSLT(s1);
}void test02()
{SLT* s1 = (SLT*)malloc(sizeof(SLT));SLT* s2 = (SLT*)malloc(sizeof(SLT));s1->val = 1;s2->val = 2;s1->next = s2;s2->next = NULL;SLT* plist = s1;SLTpushFront(&plist, 3);//頭插PrintSLT(plist);
}void test03()
{SLT* plist = NULL;//SLTpushBack(&plist, 1);//尾插//SLTpushFront(&plist, 1);//頭插//SLTpopBack(&plist);//尾刪PrintSLT(plist);
}
int main()
{test01();//test02();//test03();return 0;
}

五、手動實現雙向鏈表

根據前面的分類,雙向鏈表即帶頭雙向循環鏈表

先展示頭文件

LTNode.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>#define DateType int
//創建雙向鏈表:帶頭雙向循環鏈表
typedef struct LTNode
{DateType date;struct LTNode* prev;struct LTNode* next;
}LTNode;//初始化
void LTInit(LTNode**pphead);
//創建雙向鏈表中的節點
LTNode* BuyNode(DateType x);
//打印雙向鏈表
void PrintLT(LTNode* phead);
//雙向鏈表尾插
void LTPushBack(LTNode* phead, DateType x);
//頭插
void LTPushFront(LTNode* phead, DateType x);
//尾刪
void LTPopBack(LTNode* phead);
//頭刪
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, DateType x);
//指定位置之后插入
void LTInsert(LTNode* pos, DateType x);
//刪除指定位置的節點
void LTEarse(LTNode* pos);
//銷毀
void LTDestry(LTNode* phead);/*所有傳的都是一級指針,保證了接口的一致性,降低了記憶的成本*/

1、定義雙向鏈表的一個節點

雙向鏈表中有一個存儲Dateype類型的數據的變臉,一個用來指向下一個節點的指針next,還有一個用來指向上一個節點的指針prev。

typedef int DateType;
//定義雙向鏈表(帶頭雙向循環鏈表)的一個節點
typedef struct LTNode
{DateType date;struct LTNode* prev;//指向前一個節點struct LTNode* next;//指向后一個節點
}LTNode;

2、創建新節點

想要實現雙向鏈表的初始化,尾插,頭插以及指定位置插入數據,就避免不了要創建新的節點。由于鏈表再存儲結構上非連續,所以我們用malloc來動態申請內存空間,然后賦值,改變兩個指針的指向,使其形成自循環,最后將新節點的地址返回。

//創建雙向鏈表中的節點
LTNode* BuyNode(DateType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->date = x;newnode->prev = newnode->next = newnode;return newnode;
}

3、雙向鏈表的初始化

雙向鏈表是帶頭鏈表,所以對雙向鏈表初始化即對頭節點(哨兵位)初始化。由于哨兵位不存儲有效數據,一般將date初始化為-1,然后,改變兩個指針的指向,使其形成自循環,即直接調用創建新節點的函數。

//初始化
void LTInit(LTNode** pphead)
{*pphead = BuyNode(-1);
}

4、打印

打印則要遍歷雙向鏈表,同時,要打印的是雙向鏈表的有效節點。因此,用一個pcur指針指向頭節點的下一個節點,由于是雙向鏈表,遍歷時的結束條件就應該是pcur != phead。

//打印雙向鏈表
void PrintLT(LTNode* phead)
{//pcur指向第一個有效節點LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->date);pcur = pcur->next;}
}

5、尾插

先要找到雙向鏈表的尾節點。即phead->prev。

然后連接新節點與原來的尾節點:

phead->prev->next=newnode,newnode->prev=phead->prev

最后連接頭節點與新節點:

phead->prev=newnode,newnode->next=phead;

注意連接的順序,不然會改變phead->prev的指向。

//雙向鏈表尾插
void LTPushBack(LTNode* phead, DateType x)
{LTNode* newnode = BuyNode(x);//連接原來的尾節點與新節點phead->prev->next = newnode;newnode->prev = phead->prev;//連接頭節點與新節點phead->prev = newnode;newnode->next = phead;
}

6、頭插

頭插是指插入到第一個有效節點之前。找到第一個有效節點,然后連接頭節點,新節點與第一個節點。注意:phead->next的連接順序,防止改變這種指向。

//頭插
void LTPushFront(LTNode* phead, DateType x)
{LTNode* newnode = BuyNode(x);//連接新節點與原來第一個與有效節點newnode->next = phead->next;phead->next->prev = newnode;//連接頭節點與新節點phead->next = newnode;newnode->prev = phead;
}

7、尾刪

(1)斷言:排除傳過來NULL和空鏈表(只有一個頭節點)的情況;

(2)找到尾節點:phead->prev;再順藤摸瓜找到尾節點的前一個節點:phead->prev->prev

(3)連接頭節點與新的尾節點,即phead與phead->prev->prev;注意:連接順序

(4)釋放尾節點。

//尾刪
void LTPopBack(LTNode* phead)
{//排除傳過來NULL和空鏈表(只有一個頭節點)的情況assert(phead && phead->next != phead);LTNode* del = phead->prev;//尾節點//尾節點的前一個節點:del->prev//連接尾節點的前一個節點與頭節點del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}

8、頭刪

刪除第一個有效節點。

(1)斷言:排除傳過來NULL和空鏈表(只有一個頭節點)的情況;

(2)找到第一個有效節點:phead->next;第一個有效節點之后的節點:phead->next->next;

(3)連接頭節點與第一個有效節點之后的節點,即連接phead與phead->next->next;注意:連接順序

(4)釋放第一個有效節點。

//頭刪
void LTPopFront(LTNode* phead)
{assert(phead && phead->next != phead);LTNode* del = phead->next;//第一個有效節點//第一個有效節點之后的節點;del->next//連接頭節點與第一個有效節點之后的節點del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}

9、查找

?查找則要遍歷雙向鏈表,且要查找的是雙向鏈表的有效節點。因此,斷言排除傳過來NULL和只有一個頭節點,然后,用一個pcur指針指向頭節點的下一個節點,由于是雙向鏈表,遍歷時的結束條件就應該是pcur != phead。

//查找
LTNode* LTFind(LTNode* phead, DateType x)
{assert(phead && phead->next != phead);LTNode* std = phead->next;//讓std指針指向第一個有效節點//遍歷while (std != std->next){if (std->date == x){return std;}std = std->next;}return NULL;
}

10、指定位置之后插入

先找到指定位置之后的節點:pos->next;然后。連接pos節點,新節點newnode與指定位置之后的節點pos->next。注意:連接順序,防止改變pos->next的指向。

//指定位置之后插入
void LTInsert(LTNode* pos, DateType x)
{LTNode* newnode = BuyNode(x);//指定位置之后的節點:pos->next//連接新節點與原來指定位置之后的節點newnode->next = pos->next;pos->next->prev = newnode;//連接新節點與指定位置的節點pos->next = newnode;newnode->prev = pos;
}

11、刪除指定位置的節點

現在的指定位置節點之前的節點:pos->prev;指定位置之后的節點:pos->next。然后,連接pos->prev與pos->next。最后,釋放指定位置節點。

//刪除指定位置的節點
void LTEarse(LTNode* pos)
{//指定位置之后的節點:pos->next//指定位置之前的節點:pos->prev//連接指定位置之后的節點與指定位置之前的節點pos->prev->next = pos->next;pos->next->prev = pos->prev;//刪除pos節點free(pos);pos = NULL;
}

12、銷毀

先找到第一個有效節點std,然后用std指針遍歷鏈表,逐個節點釋放,但在釋放結構之前需要記下該節點的下一個節點,防止因為釋放而找不到,到不到銷毀整個鏈表的效果。最后將頭節點釋放。

//銷毀
void LTDestry(LTNode* phead)
{assert(phead);LTNode* std = phead->next;//讓std指針指向第一個有效節點while (std != std->next){LTNode* next = std->next;//保證在std被釋放后,std的下一個節點還能找到free(std);std = next;//下一個節點}//釋放哨兵位free(phead);phead = NULL;
}

?六、雙向鏈表完整源碼

LTNode.c

#include"LTNode.h"
//初始化
void LTInit(LTNode** pphead)
{*pphead = BuyNode(-1);
}//創建雙向鏈表中的節點
LTNode* BuyNode(DateType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->date = x;newnode->prev = newnode->next = newnode;return newnode;
}//打印雙向鏈表
void PrintLT(LTNode* phead)
{//pcur指向第一個有效節點LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->date);pcur = pcur->next;}
}//雙向鏈表尾插
void LTPushBack(LTNode* phead, DateType x)
{LTNode* newnode = BuyNode(x);//連接原來的尾節點與新節點phead->prev->next = newnode;newnode->prev = phead->prev;//連接頭節點與新節點phead->prev = newnode;newnode->next = phead;
}//頭插
void LTPushFront(LTNode* phead, DateType x)
{LTNode* newnode = BuyNode(x);//連接新節點與原來第一個與有效節點newnode->next = phead->next;phead->next->prev = newnode;//連接頭節點與新節點phead->next = newnode;newnode->prev = phead;
}	//尾刪
void LTPopBack(LTNode* phead)
{//排除傳過來NULL和空鏈表(只有一個頭節點)的情況assert(phead && phead->next != phead);LTNode* del = phead->prev;//尾節點//尾節點的前一個節點:del->prev//連接尾節點的前一個節點與頭節點del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
//頭刪
void LTPopFront(LTNode* phead)
{assert(phead && phead->next != phead);LTNode* del = phead->next;//第一個有效節點//第一個有效節點之后的節點;del->next//連接頭節點與第一個有效節點之后的節點del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}
//查找
LTNode* LTFind(LTNode* phead, DateType x)
{assert(phead && phead->next != phead);LTNode* std = phead->next;//讓std指針指向第一個有效節點//遍歷while (std != std->next){if (std->date == x){return std;}std = std->next;}return NULL;
}//指定位置之后插入
void LTInsert(LTNode* pos, DateType x)
{LTNode* newnode = BuyNode(x);//指定位置之后的節點:pos->next//連接新節點與原來指定位置之后的節點newnode->next = pos->next;pos->next->prev = newnode;//連接新節點與指定位置的節點pos->next = newnode;newnode->prev = pos;
}
//刪除指定位置的節點
void LTEarse(LTNode* pos)
{//指定位置之后的節點:pos->next//指定位置之前的節點:pos->prev//連接指定位置之后的節點與指定位置之前的節點pos->prev->next = pos->next;pos->next->prev = pos->prev;//刪除pos節點free(pos);pos = NULL;
}
//銷毀
void LTDestry(LTNode* phead)
{assert(phead);LTNode* std = phead->next;//讓std指針指向第一個有效節點while (std != std->next){LTNode* next = std->next;//保證在std被釋放后,std的下一個節點還能找到free(std);std = next;//下一個節點}//釋放哨兵位free(phead);phead = NULL;
}

test.c?

#include"LTNode.h"
void test01()
{LTNode* plist = NULL;LTInit(&plist);//初始化//LTPushBack(plist, 4);//尾插//LTPushFront(plist, 4);//頭插//LTPopBack(plist);//尾刪LTPopFront(plist);PrintLT(plist);
}
void test02()
{LTNode* s = NULL;LTInit(&s);LTNode* s1 = (LTNode*)malloc(sizeof(LTNode));LTNode* s2 = (LTNode*)malloc(sizeof(LTNode));LTNode* s3 = (LTNode*)malloc(sizeof(LTNode));s1->date = 1;s2->date = 2;s3->date = 3;//連接s->next = s1;s->prev = s3;s1->prev = s;s1->next = s2;s2->prev = s1;s2->next = s3;s3->prev = s2;s3->next = s;PrintLT(s);//打印printf("\n");//LTPushBack(s, 4);//尾插//LTPushFront(s,4);//頭插//LTPopBack(s);//尾刪//LTPopFront(s);//頭刪LTNode* ret = LTFind(s, 2);//查找//printf("%d\n", ret->date);//LTInsert(ret, 4);//指定位置之后插入LTEarse(ret);PrintLT(s);
}
int main()
{//test01();test02();return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914869.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914869.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914869.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Go語言時間控制:定時器技術詳細指南

1. 定時器基礎&#xff1a;從 time.Sleep 到 time.Timer 的進化為什么 time.Sleep 不夠好&#xff1f;在 Go 編程中&#xff0c;很多人初學時會用 time.Sleep 來實現時間控制。比如&#xff0c;想讓程序暫停 2 秒&#xff0c;代碼可能是這樣&#xff1a;package mainimport (&q…

C# 轉換(顯式轉換和強制轉換)

顯式轉換和強制轉換 如果要把短類型轉換為長類型&#xff0c;讓長類型保存短類型的所有位很簡單。然而&#xff0c;在其他情況下&#xff0c; 目標類型也許無法在不損失數據的情況下容納源值。 例如&#xff0c;假設我們希望把ushort值轉化為byte。 ushort可以保存任何0~65535的…

淺談自動化設計最常用的三款軟件catia,eplan,autocad

筆者從上半年開始接觸這三款軟件&#xff0c;掌握了基礎用法&#xff0c;但是過了一段時間不用&#xff0c;發現再次用&#xff0c;遇到的問題短時間解決不了&#xff0c;忘記的有點多&#xff0c;這里記錄一下&#xff0c;防止下次忘記Elpan:問題1QF01是柜安裝板上的一個部件&…

網絡編程7.17

練習&#xff1a;服務器&#xff1a;#include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> #include &…

c++ 模板元編程

聽說模板元編程能在編譯時計算出常量&#xff0c;簡單測試下看看&#xff1a;template<int N> struct Summation {static constexpr int value N Summation<N - 1>::value; // 計算 1 2 ... N 的值 };template<> struct Summation<1> { // 遞歸終…

【深度學習】神經網絡過擬合與欠擬合-part5

八、過擬合與欠擬合訓練深層神經網絡時&#xff0c;由于模型參數較多&#xff0c;數據不足的時候容易過擬合&#xff0c;正則化技術就是防止過擬合&#xff0c;提升模型的泛化能力和魯棒性 &#xff08;對新數據表現良好 對異常數據表現良好&#xff09;1、概念1.1過擬合在訓練…

JavaScript的“硬件窺探術”:瀏覽器如何讀取你的設備信息?

JavaScript的“硬件窺探術”&#xff1a;瀏覽器如何讀取你的設備信息&#xff1f; 在Web開發的世界里&#xff0c;JavaScript一直扮演著“幕后魔術師”的角色。從簡單的頁面跳轉到復雜的實時數據處理&#xff0c;它似乎總能用最輕巧的方式解決最棘手的問題。但你是否想過&#…

論安全架構設計(層次)

安全架構設計&#xff08;層次&#xff09; 摘要 2021年4月&#xff0c;我有幸參與了某保險公司的“優車險”項目的建設開發工作&#xff0c;該系統以車險報價、車險投保和報案理賠為核心功能&#xff0c;同時實現了年檢代辦、道路救援、一鍵挪車等增值服務功能。在本項目中&a…

滾珠導軌常見的故障有哪些?

在自動化生產設備、精密機床等領域&#xff0c;滾珠導軌就像是設備平穩運行的 “軌道”&#xff0c;為機械部件的直線運動提供穩準導向。但導軌使用時間長了&#xff0c;難免會出現這樣那樣的故障。滾珠脫落&#xff1a;可能由安裝不當、導軌損壞、超負荷運行、維護不當或惡劣環…

機器視覺的包裝盒絲印應用

在包裝盒絲網印刷領域&#xff0c;隨著消費市場對產品外觀精細化要求的持續提升&#xff0c;傳統印刷工藝面臨多重挑戰&#xff1a;多色套印偏差、曲面基材定位困難、異形結構印刷失真等問題。雙翌光電科技研發的WiseAlign視覺系統&#xff0c;通過高精度視覺對位技術與智能化操…

Redis學習-03重要文件及作用、Redis 命令行客戶端

Redis 重要文件及作用 啟動/停止命令或腳本 /usr/bin/redis-check-aof -> /usr/bin/redis-server /usr/bin/redis-check-rdb -> /usr/bin/redis-server /usr/bin/redis-cli /usr/bin/redis-sentinel -> /usr/bin/redis-server /usr/bin/redis-server /usr/libexec/red…

SVN客戶端(TortoiseSVN)和SVN-VS2022插件(visualsvn)官網下載

SVN服務端官網下載地址&#xff1a;https://sourceforge.net/projects/win32svn/ SVN客戶端工具(TortoiseSVN):https://plan.io/tortoise-svn/ SVN-VS2022插件(visualsvn)官網下載地址&#xff1a;https://www.visualsvn.com/downloads/

990. 等式方程的可滿足性

題目&#xff1a;第一次思考&#xff1a; 經典并查集 實現&#xff1a;class UnionSet{public:vector<int> parent;public:UnionSet(int n) {parent.resize(n);}void init(int n) {for (int i 0; i < n; i) {parent[i] i;}}int find(int x) {if (parent[x] ! x) {pa…

HTML--教程

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>菜鳥教程(runoob.com)</title> </head> <body><h1>我的第一個標題</h1><p>我的第一個段落。</p> </body> </html&g…

Leetcode刷題營第二十七題:二叉樹的最大深度

104. 二叉樹的最大深度 給定一個二叉樹 root &#xff0c;返回其最大深度。 二叉樹的 最大深度 是指從根節點到最遠葉子節點的最長路徑上的節點數。 示例 1&#xff1a; 輸入&#xff1a;root [3,9,20,null,null,15,7] 輸出&#xff1a;3示例 2&#xff1a; 輸入&#xff…

微信小程序翻書效果

微信小程序翻書效果 wxml <viewwx:for"{{imgList}}" hidden"{{pagenum > imgList.length - index - 1}}"wx:key"index"class"list-pape" style"{{index imgList.length - pagenum - 1 ? clipPath1 : }}"bindtouchst…

個人IP的塑造方向有哪些?

在內容創業和自媒體發展的浪潮下&#xff0c;個人IP的價值越來越受到重視。個人IP不僅是個人品牌的延伸&#xff0c;更是吸引流量來實現商業變現的重要工具。想要塑造個人IP&#xff0c;需要我們有明確的內容方向和策略&#xff0c;下面就讓我們來簡單了解下。一、展現自我形象…

Spring之【BeanDefinition】

目錄 BeanDefinition接口 代碼片段 作用 BeanDefinitionRegistry接口 代碼片段 作用 RootBeanDefinition實現類 GenericBeanDefinition實現類 BeanDefinition接口 代碼片段 public interface BeanDefinition {// ...void setScope(Nullable String scope);NullableSt…

GD32VW553-IOT LED呼吸燈項目

GD32VW553-IOT LED呼吸燈項目項目簡介這是一個基于GD32VW553-IOT開發板的LED呼吸燈演示項目。通過PWM技術控制LED亮度&#xff0c;實現多種呼吸燈效果&#xff0c;展示RISC-V MCU的PWM功能和實時控制能力。功能特性1. 多種呼吸燈效果正弦波呼吸&#xff1a;自然平滑的呼吸效果線…