數據結構——帶頭雙向循環鏈表(c語言實現)

目錄

?

1.單鏈表和雙向鏈表對比

2.雙向鏈表實現

2.1 創建新節點

2.2 鏈表初始化?

2.3 尾插?

2.4 頭插?

2.5 尾刪?

2.6 頭刪?

2.7 查找?

2.8 指定位置后插入數據?

2.9 刪除指定節點?

2.10 銷毀鏈表

2.11?打印鏈表?


?前言:

? ? ?我們在前幾期詳細地講解了不帶頭單向不循環鏈表(單鏈表),使用它的底層代碼實現了一個簡單的通訊錄項目,也介紹了鏈表分為八種,但是其中最常用的只有兩種:(1)不帶頭單向不循環鏈表,(2)帶頭雙向循環鏈表,今天我們要講解的就是第二種帶頭雙向循環鏈表

1.單鏈表和雙向鏈表對比

在介紹雙向鏈表之前,我們先來對比一下單鏈表和雙向鏈表的區別。

這是單鏈表:

這是雙向鏈表:

?

? ? ? ? 雙向鏈表的特點是每相鄰兩個節點都相互連接,每個節點都有三個部分,包括data,next,prev,其中data負責存放數據,next負責存放后一個節點的地址,prev負責存放前一個節點的地址,最后一個節點和頭節點(哨兵位)相互連接,形成了一個循環雙向鏈表,那么什么是哨兵位呢,哨兵位就是雙向鏈表的頭節點,它不存放有效數據,只存放第一個有序數據的節點的地址和最后一個有序數據節點的地址。?

? ? ? ?那么它們的區別是什么呢?

1. 無頭單向非循環鏈表: 結構簡單 ,一般不會單獨用來存數據。實際中更多是作為 其他數據結構的子結 ,如哈希桶、圖的鄰接表等等。另外這種結構在 筆試面試 中出現很多。
2. 帶頭雙向循環鏈表: 結構最復雜 ,一般用在單獨存儲數據。實際中使用的鏈表數據結構,都是帶頭雙向循環鏈表。另外這個結構雖然結構復雜,但是使用代碼實現以后會發現結構會帶來很多優勢,實現反而 簡單了,后面我們代碼實現了就知道了。

2.雙向鏈表實現

? 介紹完了雙向鏈表的區別我們接下來就要著手開始用代碼實現雙向鏈表了。由于代碼可能較多,我們將雙向鏈表的代碼分成了三個文件,分別是List.h,List.c和tste.c文件:

在list.h文件中,我們要包含我們會用到的頭文件,其他文件只要包含List.h文件就可以使用這些頭文件了:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

雙向鏈表的實現也是在此文件中,由于我們不知道將來使用鏈表會存放什么樣的數據,所以我們使用typedef對這個數據的類型改名,我們實現鏈表使用的是int類型,所以我們對int改名:

typedef int ListNodeData;

鏈表中的next和prev鏈表是用來存放節點地址的,所以它們為指針類型,而為了方便使用,我們將鏈表使用typedf改名,下面是雙向鏈表實現:

typedef struct ListNode
{ListNodeData data;struct ListNode* prev;struct ListNode* next;}LTNode;

2.1 創建新節點

創建新節點我們使用malloc從堆申請一塊LTNode類型大小的內存,它的data類型用來存放將來要插入的數據,prev和next指針在創建這個節點時先讓它指向自己,如果要創建新節點,就調用這個函數,它會返回一個指向這塊空間的指針:

LTNode* LTBuyNode(ListNodeData x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;return newnode;}//創建一個新節點

2.2 鏈表初始化?

?

在最初創建一個鏈表時,它的內部為空,什么也沒有,我們初始化應該此鏈表讓它至少有一個哨兵位:

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

2.3 尾插?

尾插操作應該先創建一個新節點,插入順序為:先讓新節點的prev指針指向最后一個節點,然后讓新節點的next指針指向哨兵位實現循環,以上的兩步都不會影響舊節點,接下來就是讓最后一個節點的next指針指向這個新節點,然后讓哨兵位的prev指針指向新節點完成尾插:

具體實現代碼為:

void LTPushBack(LTNode* phead, ListNodeData x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}//尾插

2.4 頭插?

? ? ?頭插操作并不是將新節點放在哨兵位之前,而是將新節點放在第一個有效數據節點之前,所以我們應該將新節點放在哨兵位的后面。先創建一個新節點,讓新節點的prev指針指向哨兵位,讓它的next指針指向哨兵位的next指向的節點,以上兩步不會影響任何節點,做完這兩步后,先讓哨兵位后面那個節點的prev指針指向新節點,然后讓哨兵位的next指針指向新節點,這兩步不能調換順序,否則會找不到哨兵位后面那個節點,下面是代碼實現:

void LTPushFront(LTNode* phead, ListNodeData x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;}//頭插

2.5 尾刪?

? ?執行刪除操作之前我們應該先判斷這個鏈表除哨兵位之外有沒有其他節點,如果沒有,就無法刪除,而尾刪操作也比較簡單,只需要讓尾節點的前一個節點的next指針指向哨兵位,然后讓哨兵位的prev指針指向位尾節點,以上過程需要創建一個新變量,否則無法找到我們要刪除的節點,接著釋放掉尾節點后置空就可以了:

void LTPopBack(LTNode* phead)
{assert(phead&&phead->next);LTNode* del = phead->prev;del->prev->next = del->next;del->next->prev = del->prev;free(del);del = NULL;}//尾刪

畫圖演示:

?

2.6 頭刪?

? 頭刪操作與尾刪類似,如果沒有兩個及以上節點的話無法執行刪除操作,頭刪要刪除的是哨兵位后面那個節點,所以我們先創建一個指針存放我們要刪除節點的地址,將哨兵位的next指針指向我們要刪除節點的下一個節點,然后將我們要刪除節點的下一個節點的prev指針指向哨兵位,完成這些操作后釋放我們要刪除的節點然后置空:

void LTPopFront(LTNode* phead)
{assert(phead && phead->next);LTNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}//頭刪

2.7 查找?

如果我們要查找一個節點,應該先判斷鏈表是否為空,然后將我們要查找的節點的數據與鏈表中節點的數據一一對比,如果數據內容相同,說明找到了,將這個節點返回,如果循環一圈還沒有找到,說明鏈表中不存在這樣的節點,返回應一個空指針:

LTNode* LTFind(LTNode* phead, ListNodeData x)
{assert(phead && phead->next);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//查找函數

2.8 指定位置后插入數據?

我們可以先調用查找函數,然后調用它返回的節點,試著在它的后面插入數據,而它的操作與頭插非常相像,只是將哨兵位改成了我們指定的節點:

void LTInsertAfter(LTNode* phead, LTNode* pop, ListNodeData x)
{assert(phead&&pop);LTNode* newnode = LTBuyNode(x);newnode->prev = pop;newnode->next = pop->next;pop->next->prev = newnode;pop->next = newnode;
}//指定節點后插入數據

2.9 刪除指定節點?

刪除節點我們需要先判斷鏈表中是否有兩個及以上節點,否則無法刪除,刪除指定節操作我們先將指定節點的前一個節點的next指向我們指定節點的下一個節點,然后將指定節點的下一個節點的prev指針指向指定節點的前一個節點,這個過程不需要創建中間變量,因為我們有指定節點的地址:

void LTErase(LTNode* phead, LTNode* pop)
{assert(phead && phead->next);pop->next->prev = pop->prev;pop->prev->next = pop->next;free(pop);pop = NULL;}//指定刪除節點

2.10 銷毀鏈表

我們鏈表的每一個節點都是使用malloc函數手動在堆上申請的,需要我們手動釋放:

void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}}//銷毀鏈表

2.11?打印鏈表?

指行這么多插入刪除操作我們如果測試的話就使用這個函數打印出來,而打印函數只需要循環打印這個鏈表一次就可以了:

void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");}//打印

接下來我們使用這個函數來測試一下我們的方法:

可以看到我們的方法都沒有問題,那么這期的雙向鏈表就到此結束啦,我將代碼放在下面,感興趣的小伙伴可以試試哦。

List.h :

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int ListNodeData;
typedef struct ListNode
{ListNodeData data;struct ListNode* prev;struct ListNode* next;}LTNode;LTNode* LTFind(LTNode* phead, ListNodeData x);//查找void LTInit(LTNode** pphead);//初始化void LTPushBack(LTNode* phead, ListNodeData x);
//尾插void LTPrint(LTNode* phead);//打印鏈表void LTPushFront(LTNode* phead, ListNodeData x);
//頭插void LTPopBack(LTNode* phead);//尾刪void LTPopFront(LTNode* phead);//頭刪void LTInsertAfter(LTNode* phead, LTNode* pop, ListNodeData x);
//指定節點后刪除void LTErase(LTNode* phead, LTNode* pop);
//指定位置刪除void LTDestroy(LTNode* phead);
//銷毀鏈表

List.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"LTNode* LTBuyNode(ListNodeData x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;return newnode;}//創建一個新節點LTNode* LTFind(LTNode* phead, ListNodeData x)
{assert(phead && phead->next);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//查找函數void LTInit(LTNode** pphead)
{assert(pphead);*pphead = LTBuyNode(-1);}//初始化void LTPushBack(LTNode* phead, ListNodeData x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}//尾插void LTPushFront(LTNode* phead, ListNodeData x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;}//頭插void LTPopBack(LTNode* phead)
{assert(phead&&phead->next);LTNode* del = phead->prev;del->prev->next = del->next;del->next->prev = del->prev;free(del);del = NULL;}//尾刪void LTPopFront(LTNode* phead)
{assert(phead && phead->next);LTNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}//頭刪void LTInsertAfter(LTNode* phead, LTNode* pop, ListNodeData x)
{assert(phead&&pop);LTNode* newnode = LTBuyNode(x);newnode->prev = pop;newnode->next = pop->next;pop->next->prev = newnode;pop->next = newnode;
}//指定節點后插入數據void LTErase(LTNode* phead, LTNode* pop)
{assert(phead && phead->next);pop->next->prev = pop->prev;pop->prev->next = pop->next;free(pop);pop = NULL;}//指定刪除節點void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");}//打印void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}}//銷毀鏈表

test.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void test01()
{LTNode* plist = NULL;LTInit(&plist);printf("尾插\n");LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPrint(plist);printf("頭插\n");LTPushFront(plist, 0);LTPrint(plist);/*printf("尾刪\n");LTPopBack(plist);LTPopBack(plist);LTPrint(plist);*/printf("頭刪\n");LTPopFront(plist);LTPopFront(plist);LTPrint(plist);printf("在3后面插入數據:\n");LTNode* Find = LTFind(plist, 3);/*if (Find == NULL){printf("找不到!\n");}else{printf("找到了\n");}*/LTInsertAfter(plist, Find, 56);LTPrint(plist);printf("刪除指定節點3:\n");LTErase(plist, Find);LTPrint(plist);printf("銷毀鏈表\n");LTDestroy(plist);plist = NULL;}int main()
{test01();return 0;
}

?

?

?

?

?

?

?

?

?

?

?

?

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

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

相關文章

vue下載本地xls模版靜態文件

需求導入的下載模版不想放在服務器放在前端本地下載靜態資源最簡單的方式直接訪問 public 文件夾下的文件 方法一&#xff1a;使用靜態文件路徑 將文件放在 public 文件夾中&#xff1a; 把你的文件從 src/assets 移動到 public 文件夾。例如&#xff1a;public/template.xls。…

【高考志愿】電氣工程

目錄 一、專業概述 二、專業特點 三、就業前景 四、選擇學校 高考志愿選擇電氣工程是一個極具智慧和遠見的決定&#xff0c;因為電氣工程在當今社會中扮演著至關重要的角色。以下是對電氣工程專業更為詳細的解析&#xff1a; 一、專業概述 電氣工程及其自動化專業&#xf…

一個項目學習Vue3---快速認識JSX

JSX&#xff08;JavaScript XML&#xff09;是一種用于在React框架中編寫UI組件的語法擴展。它允許開發者將HTML標記直接嵌入到JavaScript代碼中&#xff0c;使得在React組件中編寫界面變得更加直觀和高效。在編譯過程中&#xff0c;JSX會被轉換成普通的JavaScript對象&#xf…

工業液晶屏G065VN01 V2規格書簡介

G065VN01 V2 背面實物圖 2. 概述 G065VN01 V2 專為 VGA &#xff08;640 x RGB x 480&#xff09; 分辨率和 16.2M&#xff08;RGB 6 位 FRC&#xff09;或 262k 色&#xff08;RGB 6 位&#xff09;的工業顯示應用而設計。它由TFT-LCD面板、驅動IC、控制和電源電路板以及包括…

css3實現水紋進度條

其實有一個mask-image屬性 挺有意思&#xff0c;在元素上面實現遮罩層的效果&#xff0c;不過這玩意有些兼容性問題 需要處理&#xff0c;所以單純可以通過漸變色的方式來實現 同時加上動畫效果 .jianbian {width: 100%;height: 16px;background-color: #eee;display: flex;bor…

華三中小企業組網

一、組網需求 在中小園區中&#xff0c;S5130系列或S5130S系列以太網交換機通常部署在網絡的接入層&#xff0c;S5560X系列或 S6520X系列以太網交換機通常部署在網絡的核心&#xff0c;出口路由器一般選用MSR系列路由器。 核心交換機配置VRRP保證網絡可靠性。園區網中不同的…

MySQL進階——鎖

目錄 1全局鎖—一致性數據備份 1.1全局鎖介紹 1.2語法 1.3 一致性備份案例 1.4 全局鎖特點 2表級鎖 2.1表鎖 2.1.1共享讀鎖 2.1.2獨占寫鎖 2.2元數據鎖 2.3元數據鎖 MySQL中的鎖&#xff0c;按照鎖的粒度分&#xff0c;分為以下三類&#xff1a; &#xff08;1&…

GitLab配置免密登錄之后仍然需要Git登錄的解決辦法

GitLab配置免密登錄之后仍然需要Git登錄的解決辦法 因為實習工作需要&#xff0c;要在本地拉取gitlab上的代碼&#xff0c;設置了密鑰之后連接的時候還需要登錄的token&#xff0c;摸索之后有了下面的解決辦法。 方法一&#xff1a; 根據報錯的提示&#xff0c;去網站上設置個人…

動手學自然語言處理:解讀大模型背后的核心技術

自從 ChatGPT 橫空出世以來&#xff0c;自然語言處理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09; 研究領域就出現了一種消極的聲音&#xff0c;認為大模型技術導致 NLP “死了”。在某乎上就有一條熱門問答&#xff0c;大家熱烈地討論了這個問題。 有…

【STM32】看門狗

1.看門狗簡介 看門狗起始就是一個定時器&#xff0c;從功能上說它可以讓微控制器在程序發生意外&#xff08;程序進入死循環或跑飛&#xff09;的時候&#xff0c;能重新恢復到系統剛上電狀態&#xff0c;以保障系統出問題的時候可以重啟一次。說的簡單一點&#xff0c;看門狗…

用英文介紹孟買:Mumbai India‘s Transforming MEGACITY

Mumbai: India’s Transforming MEGACITY Link: https://www.youtube.com/watch?vtWD_-Rzrn8o Summary First Paragraph: Mumbai, India’s financial and entertainment capital, is undergoing a major transformation. With its contiguous urban population nearing 25…

神經網絡實現AND門:邏輯運算的智能化飛躍

神經網絡實現AND門&#xff1a;邏輯運算的智能化飛躍 在人工智能的早期探索中&#xff0c;人們就夢想著用機器模擬人腦的邏輯思考能力。AND邏輯函數作為最基本的邏輯運算之一&#xff0c;其在神經網絡中的實現&#xff0c;標志著我們向智能化邁出了堅實的一步。本文將詳細解釋…

web圖片怎么導入ps?這個方法給你輕松解決!

隨著WebP格式圖片因其體積小、加載快的優勢在網站中日益普及&#xff0c;對于圖片編輯者來說&#xff0c;能夠直接在Photoshop中打開和編輯WebP文件變得尤為重要。 WebPShop插件應運而生&#xff0c;它是一個專為Photoshop設計的模塊&#xff0c;支持打開和保存WebP圖像&#…

ATFX匯市:澳大利亞5月CPI大增0.4百分點,降息預期顯著降溫

ATFX匯市&#xff1a;據澳大利亞統計局數據&#xff0c;澳大利亞5月加權CPI年率為4%&#xff0c;高于前值3.6%&#xff0c;高于預期3.8%&#xff0c;顯示出澳大利亞通脹率頗具韌性。5月份數據公布之前&#xff0c;月度CPI年率平均波幅不足0.1個百分點&#xff0c;呈現出橫盤震蕩…

《數字圖像處理》實驗報告六

一、實驗任務與要求 比較采用不同的色彩空間對彩色圖像處理的效果&#xff0c;處理包括&#xff1a; a&#xff09;直方圖均衡化 b&#xff09;圖像增強 二、實驗報告 &#xff08;一&#xff09;RGB色彩空間的直方圖均衡化 / 銳化處理 1、matlab 實現代碼&#xff1a; %…

C語言 用getchar函數讀入兩個字符給c1和c2,然后分別用putchar函數和printf函數輸出

用getchar函數讀入兩個字符給c1和c2&#xff0c;然后分別用putchar函數和printf函數輸出這兩個字符并且解答以下三個問題&#xff1a; 1.變量c1和c2應定義為字符型&#xff0c;整形&#xff0c;還是二者皆可&#xff1f; 2.要求輸出c1和C2的ASCII碼&#xff0c;應如何處理&am…

推薦系統(LLM去偏?) | (WSDM24)預訓練推薦系統:因果去偏視角

::: 大家好&#xff01;今天我分享的文章是來自威斯康星大學麥迪遜分校和亞馬遜AWS AI實驗室的最新工作&#xff0c;文章所屬領域是推薦系統和因果推理&#xff0c;作者針對跨域推薦中的偏差問題提出了一種基于因果去偏的預訓練推薦系統框架PreRec。 ::: 原文&#xff1a;Pre-t…

【MySQL進階之路 | 小結篇】MySQL鍵約束KEY與索引INDEX

1. 鍵約束 關鍵字key 比如UNIQUE KEY就是一個唯一性約束&#xff0c;用于確保表中的某一列或多列的組合具有唯一性&#xff0c;不允許有重復值.當定義一個唯一性約束的時候&#xff0c;會自動創建一個唯一性索引來支持這一約束&#xff0c;這意味著它同時也起到了索引的作用.…

mobaXterm上傳文件進度一直為0%

在這里修改了senssion、重啟都沒有用 最后發現是文件存放的路徑中不能有中文&#xff0c;改了之后就成功上傳了

開展FMEA培訓時需要做好哪些準備?

FMEA&#xff08;失效模式與影響分析&#xff09;作為一種預防性的質量工具&#xff0c;正逐漸成為當代企業提升產品競爭力的關鍵。然而&#xff0c;很多企業在開展FMEA時&#xff0c;卻常常因為準備工作不足而事倍功半。那么&#xff0c;開展FMEA培訓時需要做好哪些準備呢&…