雙向鏈表 -- 詳細理解和實現

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?歡迎光顧我的homepage??????????????????????????????????????????????????????????????

前言

????????雙向鏈表是一種帶頭雙向循環的鏈表。在雙向鏈表中,首先存在著一個頭結點;其次每個節點有指向下一個節點的指針next 和指向上一個節點的指針prev ;最后,雙向鏈表的頭結點中存放上一個節點的指針指向鏈表的尾節點,尾節點中存放下一個節點的指針指向鏈表的頭結點,形成一個閉環。這樣雙向鏈表既可以從前遍歷,也可以從后遍歷,直到回到起點。

一、鏈表的分類

? ? ? ? 鏈表的結構多種多樣,鏈表呢可以是帶頭(不帶頭)、雙向(單向)、循環(不循環)的,我們之前實現的單鏈表其實就是不帶頭,單向,不循環的鏈表。

而這些有什么區別呢?

????????帶頭和不帶頭

這里帶頭和不帶頭指的是有沒有頭結點,這單鏈表的實現過程中,是直接創建一個指針變量指向鏈表的第一個節點(這是沒有頭結點的情況),而存在頭結點呢,就是一個哨兵位,里面沒有存放數據,存放了指向第一個節點的指針。

? ? ? ? 可以看到,帶頭的鏈表多了一個節點(head),這個節點中沒有存放任何數據,這樣做可以方便對鏈表的節點進行統一操作。

? ? ? ? 單向和雙向

????單向是可以通過一個節點找到它的后一個節點,而雙向是可以通過一個節點找到它前后的節點。

? ? ? ? 循環和不循環

? ? 這實現單鏈表的時候,我們將鏈表的最后一個節點next指針置位了空指針(NULL),而循環的鏈表中,我們會將最后一個節點的next指針指向鏈表的頭結點,對于雙向鏈表,將頭節點的prev(上一個節點)指針指向鏈表的尾節點。

二、雙向鏈表的實現

這里實現的雙向鏈表,先來看一下雙向鏈表的節點結構

雙向鏈表節點

typedef int LTDataType;
//雙向鏈表
typedef struct ListNode
{struct ListNode* prev;  //指向上一個節點struct ListNode* next;  //指向下一個節點LTDataType data;
}LTNode;

雙向鏈表的功能預覽

//初始化并創建頭結點
void LTInit(LTNode** phead);
//LTNode* LTInit();
//輸出
void LTPrint(LTNode* phead);
//創建新的節點
LTNode* LTBuyNode(LTDataType x);
//頭插
void LTPushFront(LTNode* phead, LTDataType x);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//頭刪
void LTPopFront(LTNode* phead);
//尾刪
void LTPopBack(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType find);
//在pos節點之后插入數據
void LTPushAfter(LTNode* pos, LTDataType x);
//在pos節點之前插入數據
void LTPush(LTNode* pos, LTDataType x);
//刪除pos節點
void LTPop(LTNode* pos);
//鏈表銷毀
void LTDesTroy(LTNode* phead);

2.1、創建新的節點

? ? ? ? 創建節點,和單鏈表一樣,都是動態開辟的空間。

//創建新的節點
LTNode* LTBuyNode(LTDataType x)
{LTNode* ptail = (LTNode*)malloc(sizeof(LTNode));if (ptail == NULL){perror("LTBuyNode--malloc filed!");exit(1);}ptail->data = x;ptail->prev = ptail->next = NULL;return ptail;
}

2.2、雙向鏈表初始化并創建頭結點

? ? ? ? 鏈表初始化,其實就是創建一個頭節點(也叫哨兵位);因為這里是雙向鏈表,創建完頭節點之后,讓頭節點的prev和next指針都指向它自己。

//初始化并創建頭結點
void LTInit(LTNode** phead)
{*phead = LTBuyNode(-1);(*phead)->prev = (*phead)->next = *phead;
}

2.3、輸出鏈表

? ? ? ? 遍歷鏈表并輸出有效數據,這里雙向鏈表可以從前開始遍歷也可以從后開始遍歷。

//輸出
void LTPrint(LTNode* phead)
{LTNode* ptail = phead->next;//遍歷雙向鏈表 -- 從前開始遍歷while (ptail != phead){printf("%d -> ", ptail->data);ptail = ptail->next;}printf("\n");
}

2.4、雙向鏈表頭插數據

? ? ? ? 從鏈表的頭部插入數據,創建新的節點,然后將新的節點prev指針指向頭節點,將next指針指向頭節點的下一個節點;然后修改頭節點的next指針和頭節點下一個節點的prev指針即可。

//頭插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead;newnode->next = phead->next;//修改前后節點的指針指向phead->next->prev = newnode;phead->next = newnode;
}

2.5、雙向鏈表尾插數據

? ? ? ? 從鏈表尾部插入到尾節點的后面,創建新的節點,將新節點的prev指針指向頭節點的上一個節點,將next指針指向頭節點,然后修改尾節點的next指針和頭節點的prev指針即可。

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;//修改前后節點指針指向phead->prev->next = newnode;phead->prev = newnode;
}

2.6、雙向鏈表頭刪數據

? ? ? ? 頭刪是刪除頭節點的后一個節點,因為是動態開辟的內存,需要內存釋放;刪除后,讓頭節點的next指針 指向下一個數據的節點。

//頭刪
void LTPopFront(LTNode* phead)
{assert(phead && phead != phead->next); //鏈表為空的話就不能進行刪除LTNode* del = phead->next;  phead->next->next->prev = phead;phead->next = phead->next->next;free(del);del = NULL;
}

2.7、雙向鏈表尾刪數據

? ? ? ? 尾插是刪除鏈表的尾節點,釋放內存之后,讓尾節點的上一個節點next指針指向頭節點,頭節點的prev指針指向刪除節點的上一個節點。

//尾刪
void LTPopBack(LTNode* phead)
{assert(phead && phead->next != phead); //鏈表為空不能進行刪除LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}

2.8、雙向鏈表查找數據

? ? ? ? 這里如果找到了,就返回該節點的指針,如果沒有就返回NULL;方便對其進行前后插入數據和刪除。

//查找
LTNode* LTFind(LTNode* phead, LTDataType find)
{assert(phead);LTNode* ptail = phead->next;while (ptail != phead) //遍歷鏈表{if (ptail->data == find) {return ptail;}ptail = ptail->next;}return NULL;
}

2.9、在指定節點pos之前插入數據

? ? ? ? 根據我們查找到的節點,在其之前插入數據,首先創建新節點,將新節點的prev指針指向pos的前一個節點,新節點的next指針指向pos;再修改pos的上一個節點的next指針指向和pos的prev指針指向即可;

//在pos節點之前插入數據
void LTPush(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos;newnode->prev = pos->prev;//修改pos前后節點指針的指向pos->prev->next = newnode;pos->prev = newnode;
}

? ? ? ? 這里就可以發現雙向鏈表的一個好處,不用像單向鏈表那樣遍歷鏈表來尋找pos的上一個節點。

2.10、在指定節點pos之后插入數據

? ? ? ? 根據查找到的節點,在其之后插入數據;首先創建節點,將新節點的prev指針指向pos,新節點的next指針指向pos的下一個節點;然后修改pos的next指針指向和pos下一個節點的prev指針指向即可。

//在pos節點之后插入數據
void LTPushAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;//修改pos前后節點指針的指向pos->next->prev = newnode;pos->next = newnode;
}

2.11、刪除指定節點pos

? ? ? ? 根據查找到的節點,然后將其刪除,這里需要修改pos前后節點的指針指向。

//刪除pos節點
void LTPop(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}

2.12、雙向鏈表的銷毀

? ? ? ? 及時對動態開辟的內存進行釋放,養成好習慣

? ? ? ? 現在,動態開辟的鏈表需要進行銷毀(也就是動態內存釋放),這里就需要遍歷鏈表了。

//鏈表銷毀
void LTDesTroy(LTNode* phead)
{assert(phead);LTNode* ptail = phead->next;while (ptail != phead){LTNode* del = ptail->next;free(ptail);ptail = del;}free(ptail);ptail = NULL;
}


代碼總覽

List.h

#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
//雙向鏈表
typedef struct ListNode
{struct ListNode* prev;  //指向上一個節點struct ListNode* next;  //指向下一個節點LTDataType data;
}LTNode;//初始化并創建頭結點
void LTInit(LTNode** phead);
//LTNode* LTInit();
//輸出
void LTPrint(LTNode* phead);
//創建新的節點
LTNode* LTBuyNode(LTDataType x);
//頭插
void LTPushFront(LTNode* phead, LTDataType x);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//頭刪
void LTPopFront(LTNode* phead);
//尾刪
void LTPopBack(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType find);
//在pos節點之后插入數據
void LTPushAfter(LTNode* pos, LTDataType x);
//在pos節點之前插入數據
void LTPush(LTNode* pos, LTDataType x);
//刪除pos節點
void LTPop(LTNode* pos);
//鏈表銷毀
void LTDesTroy(LTNode* phead);

List.c

#include"List.h"//創建新的節點
LTNode* LTBuyNode(LTDataType x)
{LTNode* ptail = (LTNode*)malloc(sizeof(LTNode));if (ptail == NULL){perror("malloc filed!");exit(1);}ptail->data = x;ptail->prev = ptail->next = NULL;return ptail;
}
//初始化并創建頭結點
void LTInit(LTNode** phead)
{*phead = LTBuyNode(-1);(*phead)->prev = (*phead)->next = *phead;
}
//輸出
void LTPrint(LTNode* phead)
{LTNode* ptail = phead->next;//遍歷雙向鏈表 -- 從前開始遍歷while (ptail != phead){printf("%d -> ", ptail->data);ptail = ptail->next;}printf("\n");
}
//頭插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead;newnode->next = phead->next;//修改前后節點的指針指向phead->next->prev = newnode;phead->next = newnode;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;//修改前后節點指針指向phead->prev->next = newnode;phead->prev = newnode;
}
//頭刪
void LTPopFront(LTNode* phead)
{assert(phead && phead != phead->next); //鏈表為空的話就不能進行刪除LTNode* del = phead->next;  phead->next->next->prev = phead;phead->next = phead->next->next;free(del);del = NULL;
}
//尾刪
void LTPopBack(LTNode* phead)
{assert(phead && phead->next != phead); //鏈表為空不能進行刪除LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
//查找
LTNode* LTFind(LTNode* phead, LTDataType find)
{assert(phead);LTNode* ptail = phead->next;while (ptail != phead) //遍歷鏈表{if (ptail->data == find) {return ptail;}ptail = ptail->next;}return NULL;
}
//在pos節點之前插入數據
void LTPush(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos;newnode->prev = pos->prev;//修改pos前后節點指針的指向pos->prev->next = newnode;pos->prev = newnode;
}
//在pos節點之后插入數據
void LTPushAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;//修改pos前后節點指針的指向pos->next->prev = newnode;pos->next = newnode;
}

感謝各位大佬支持并指出問題,

????????????????如果本篇內容對你有幫助,可以一鍵三連支持以下,感謝支持!!!

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

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

相關文章

Trimble realworks 2024.02 中文激活版獲取License下載軟件

Trimble realworks 2024 是領先的3D點云和2D圖像處理解決方案&#xff0c;使用可您提供了一組用于處理的工具&#xff0c;以便為您的應用程序&#xff08;或項目&#xff09;獲取必要的信息。此處理可以分為三種模式&#xff0c;在注冊中&#xff0c;您可以注冊相對于其他掃描和…

通信協議_Modbus協議簡介

概念介紹 Modbus協議&#xff1a;一種串行通信協議&#xff0c;是Modicon公司&#xff08;現在的施耐德電氣Schneider Electric&#xff09;于1979年為使用可編程邏輯控制器&#xff08;PLC&#xff09;通信而發表。Modbus已經成為工業領域通信協議的業界標準&#xff08;De f…

大舍傳媒:如何在海外新聞媒體發稿報道摩洛哥?

引言 作為媒體行業的專家&#xff0c;我將分享一些關于在海外新聞媒體發稿報道摩洛哥的干貨教程。本教程將帶您深入了解三個重要的新聞媒體平臺&#xff1a;Mediterranean News、Morocco News和North African News。 地中海Mediterranean News Mediterranean News是一個知名…

合合信息大模型“加速器”重磅上線

大模型技術的發展和應用&#xff0c;預示著更加智能化、個性化未來的到來。如果將大模型比喻為正在疾馳的科技列車&#xff0c;語料便是珍貴的“燃料”。本次世界人工智能大會期間&#xff0c;合合信息為大模型打造的“加速器”解決方案備受關注。 在大模型訓練的上游階段&…

【計算機畢業設計】021基于weixin小程序微信點餐

&#x1f64a;作者簡介&#xff1a;擁有多年開發工作經驗&#xff0c;分享技術代碼幫助學生學習&#xff0c;獨立完成自己的項目或者畢業設計。 代碼可以私聊博主獲取。&#x1f339;贈送計算機畢業設計600個選題excel文件&#xff0c;幫助大學選題。贈送開題報告模板&#xff…

Python學習中使用循環(for, while)

在Python編程語言中&#xff0c;循環是一個非常重要的概念&#xff0c;可以幫助我們在代碼中重復執行某些操作。Python支持兩種主要的循環結構&#xff1a;for 循環和 while 循環。 1. for 循環 for 循環用于遍歷一個序列&#xff08;如列表、元組、字符串&#xff09;或其他…

第11章:標準化和軟件知識產權

第11章&#xff1a;標準化和軟件知識產權 標準化 國際標準(International Standard)是指國際標準化組織(ISO)、國際電工 委員會(IEC)所制定的標準。 標準 是對重復性事物和概念所做的統一規定。 標準化的特征包括橫向綜合性、政策性和統一性 。 標準化是指在經濟、技術、科學…

JAVA學習-練習試用Java實現“分發糖果”

問題&#xff1a; 老師想給孩子們分發糖果&#xff0c;有 N 個孩子站成了一條直線&#xff0c;老師會根據每個孩子的表現&#xff0c;預先給他們評分。 需要按照以下要求&#xff0c;幫助老師給這些孩子分發糖果&#xff1a; 每個孩子至少分配到 1 個糖果。 評分更高的孩子…

FastAPI:高性能異步API框架

文章目錄 引言官網鏈接FastAPI 原理1. 基于 Starlette 和 Pydantic2. 路由與依賴注入3. 自動文檔 使用方法安裝 FastAPI創建一個簡單的API運行服務器 優缺點優點缺點 結論 引言 在快速發展的Web和移動應用時代&#xff0c;構建高效、可擴展的API成為了現代軟件開發的關鍵需求之…

Thingsboard 系列之通過 ESP8266+MQTT 模擬設備上報數據到平臺

前置工作 Thingsboard平臺ESP 8266 NodeMCU 開發板IDE&#xff1a; Arduino 或 VScode 均可 服務端具體對接流程 系統管理員賬號通過 Thingsboard 控制面板創建租戶等信息并以租戶賬號登錄 實體 —> 設備維護具體設備信息 創建完成后通過管理憑據修改或直接復制訪問令牌…

python 冷知識 66 個 0708

66個有趣的Python冷知識 內聯注釋 可以在代碼行尾使用 # 進行內聯注釋&#xff0c;例如 x 10 # 這是一個內聯注釋。 多行注釋 多行注釋可以用三個引號 或 """ 包裹。 分數 fractions 模塊提供了分數類型&#xff0c;可以精確表示分數值。 小數 decimal 模塊…

致遠OA同步組織架構到企業微信

致遠OA同步組織架構到企業微信 可適配任何系統 背景 原有的微協同無法滿足人員同步&#xff0c;因為在啟用微協同的時候&#xff0c;企業微信已經存在人員&#xff0c;所以配置微協同之后&#xff0c;人員會出現新增而不會同步修改 方案 重寫同步&#xff0c;針對已經存在…

Visual Studio下安裝引入Boost庫

背景&#xff1a; 在 Win 上通過 Visual Studio 運行 c 代碼&#xff0c;引入頭文件 #include <boost/...>&#xff0c;顯式無法打開&#xff0c;需要手動下載boost并進行配置。 1、下載boost&#xff1a; Boost官網&#xff1a;Boost Downloads 下載boost&#xff0c…

網安加·百家講壇 | 關昕健:新時代企業數據安全運營思路

作者簡介&#xff1a;關昕健&#xff0c;某運營商安全專家&#xff0c;2015年獲CISSP認證&#xff0c;長期負責企業安全運營工作&#xff0c;關注國內外數據安全動態與解決方案&#xff0c;持續開展數據安全運營實踐。 近年來&#xff0c;隨著《數據安全法》的出臺和國家數據局…

Pytorch中的DataLoader類

&#x1f4da;博客主頁&#xff1a;knighthood2001 ?公眾號&#xff1a;認知up吧 &#xff08;目前正在帶領大家一起提升認知&#xff0c;感興趣可以來圍觀一下&#xff09; &#x1f383;知識星球&#xff1a;【認知up吧|成長|副業】介紹 ??如遇文章付費&#xff0c;可先看…

js逆向案例 | 加速樂反爬逆向

前言 加速樂作為一種常見的反爬蟲技術&#xff0c;在網絡上已有大量詳盡深入的教程可供參考。然而&#xff0c;對于那些初次接觸的人來說&#xff0c;直接面對它可能仍會感到困惑。 聲明 本文僅用于學習交流&#xff0c;學習探討逆向知識&#xff0c;歡迎私信共享學習心得。如…

oracle19 數據庫介紹

1.1Oracle數據庫概念和應用 每個人家里都會有冰箱&#xff0c;冰箱是用來干什么的&#xff1f;冰箱是用來存放食物的地方。同樣的&#xff0c;數據庫是存放數據的地方。正是因為有了數據庫后&#xff0c;可以直接查找數據。例如你每天使用余額寶查看自己的賬戶收益&#xff0c;…

【YOLOv5/v7改進系列】改進池化層為RFB

一、導言 論文 "Receptive Field Block Net for Accurate and Fast Object Detection" 中提出的 RFB (Receptive Field Block) 模塊旨在模仿人類視覺系統中的感受野結構&#xff0c;以增強深度學習模型對不同尺度和位置的目標檢測能力。下面總結了RFB模塊的主要優點…

MySQL數據庫巡檢步驟

MySQL巡檢 系統基本信息 機型號 IP CPU 內存 磁盤 (業務)系統信息 操作系統 主機名 操作系統巡檢 檢查內容 說明 檢查方法 結果&#xff08;異常需詳細說明&#xff09; 正常輸出結果 系統配置檢查 操作系 統版本 #uname –a □正常 □異常 顯示系統版本和核心補丁信…

AIGC時代程序員的躍遷——編程高手的密碼武器

&#x1f49d;&#x1f49d;&#x1f49d;歡迎來到我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:kwan 的首頁,持續學…