數據結構--單鏈表實現

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

前言????????

????????鏈表和順序表都是線性表的一種,但是順序表在物理結構和邏輯結構上都是連續的,但鏈表在邏輯結構上是連續的,而在物理結構上不一定連續;來看以下圖片來認識鏈表與順序表的差別

這里以動態順序表為例,和鏈表中的單鏈表對比一下

動態順序表

單鏈表

? ? ? ? 這里就可以很清晰的看到順序表的底層其實就是一個數組,數據的是連續存儲的(順序表物理結構連續);而鏈表它每一個數據都不是連續的(鏈表物理結構上不一定連續)。

鏈表節點

? ? ? ? 通過觀察上圖,我們會發現鏈表每一個節點都存放在數據和下一個節點的地址。

????????那么來想一下,為了鏈表每一個節點都統一起來,都存儲有效數據和下一個節點的地址,我們就需要創建應該結構體,來存儲有效數據和下一個節點的指針;
注:這里只是單鏈表

typedef int SLType;
typedef struct SLTNode
{SLType data;struct SLTNode* next;
}SLT;

創建好鏈表節點,接下來就來實習單鏈表存儲數據的這些功能。

單鏈表實現

先來看一下單鏈表都實現都哪些功能

//輸出鏈表
void SLTPrint(SLT* phead);
//創建節點
SLT* SLTCreat(SLType x);
//單鏈表頭插
void SLTPushFront(SLT** pphead, SLType x);
//單鏈表尾插
void SLTPushBack(SLT** pphead, SLType x);
//單鏈表頭刪
void SLTPopFront(SLT** pphead);
//單鏈表尾刪
void SLTPopBack(SLT** pphead);
//查找數據
SLT* SLTFind(SLT* phead, SLType x);
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x);
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x);
//刪除指定節點
void SLTErase(SLT** pphead, SLT* pos);
//刪除指定位置后一個節點
void SLTEraseAfter(SLT* pos);

創建節點

? ? ? ? 這里創建節點,還是使用動態內存來創建,創建完成后,將數據存儲進去,并把新節點的下一個節點置為NULL

代碼如下:

//創建節點
SLT* SLTCreat(SLType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}

? ? ? ? 測試一下代碼是否正確

可以看到代碼沒有問題。

輸出鏈表

? ? ? ? 由于這里實現單鏈表,存儲的是整型數據,就以整型的方式輸出,若存儲其他類型的數據,就以存儲類型的方式輸出。

輸出鏈表,首先就要遍歷鏈表,因為鏈表最后一個節點里存儲的下一個節點的地址為空(即最后一個節點? ->next 為空)所以,遍歷鏈表結束的條件就是ptail?==NULL,沒輸出一個就讓ptail指向下一個節點,直到為空,遍歷結束。

? ? ? ? 來寫代碼實現:

//輸出鏈表
void SLTPrint(SLT* phead)
{SLT* ptail = phead;while (ptail!= NULL)//也可以直接寫成 ptail{printf("%d -> ", ptail->data);ptail = ptail->next;}printf("NULL\n");
}

這里先創建兩個節點并存儲數據輸出看一下結果

int main()
{SLT* plist = SLTCreat(1);plist->next = SLTCreat(2);SLTPrint(plist);return 0;
}

? ? ? ? 這里也成功輸出插入的兩個數據。(最后一個節點的next指向空)

單鏈表頭插

? ? ? ? 在鏈表頭部插入數據,不用像順序表那樣去移動所以的有效數據,鏈表只需要改變一個指針的指向即可

假設現在鏈表中已經存在兩個數據,再進行頭插,這時就只需要改變plist的指向即可,當然新節點的next指針也要指向下一個節點(plist指向的節點)。

代碼如下:

//單鏈表頭插
void SLTPushFront(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);newnode->next = *pphead;*pphead = newnode;
}

還有一種情況,如果現在鏈表中沒有數據,再進行頭插,這里代碼也能實現鏈表沒有數據時的頭插

單鏈表尾插

? ? ? ? 鏈表的尾插,因為指針傳的是指向頭節點的指針的地址,所以,我們需要先遍歷鏈表,找到鏈表的尾部,再修改尾節點的next指針指向。

假設現在鏈表中已經存在兩個數據,再進行尾插,此時我們只需要找到鏈表的尾部,修改尾節點next指針指向即可,代碼如下

//單鏈表尾插
void SLTPushBack(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);SLT* ptail = *pphead;//遍歷鏈表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;
}

????????考慮了這種情況,再來看以下鏈表為空的情況,如果鏈表為空,這里ptail->next就對空指針進行解引用操作了;所以,我們需要判斷鏈表是否為空?如果為空,插入的新節點就是頭節點,直接讓plist(即*pphead)指向新節點即可;如果不為空就正常進行尾插。

最終代碼如下:

//單鏈表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	assert(pphead);SLT* newnode = SLTCreat(x);if (*pphead == NULL) //判斷鏈表是否為空{*pphead = newnode;}else {SLT* ptail = *pphead;//遍歷鏈表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}

這里代碼可以正常進行尾插。

單鏈表頭刪

? ? ? ? 鏈表頭刪,首先我們要判斷鏈表是否為空,如果為空(空鏈表沒有數據該如何刪除呢?

鏈表頭刪,我們需要修改plist(*pphead)指向鏈表的下一個節點即頭節點的next指針。

? ? ? ? 此外:因為我們的節點都是動態申請的內存,所以在刪除時,需要把它釋放掉。

思路很簡單,寫一下代碼:
?

//單鏈表頭刪
void SLTPopFront(SLT** pphead)
{assert(pphead && *pphead);SLT* del = (*pphead);*pphead = (*pphead)->next;free(del);del = NULL;
}

再來看一個如果鏈表為空,又是啥結果呢?

現在鏈表已經為空,在執行一次頭刪代碼

這里assert斷言報錯。

單鏈表尾刪

? ? ? ? 首先尾刪與頭刪一樣,鏈表不能為空。

? ? ? ? 尾刪與尾插也有共同之處,就是遍歷鏈表,找到鏈表的尾節點。找到尾節點,刪除尾節點;然后修改尾節點上一個節點的next指針為NULL;所以在遍歷鏈表時就要多記錄一個節點。

//單鏈表尾刪
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);SLT* ptail = *pphead;SLT* pcur = *pphead;//遍歷鏈表while (ptail->next){pcur = ptail;ptail = ptail->next;}pcur->next = NULL;free(ptail);ptail  = NULL;
}

????????在測試的時候我們會發現一個問題,如果鏈表只有一個節點,刪除之后我們的plist指針并沒有置為空,而我們的空間已經釋放掉了,這是很危險的;所以這里我們先判斷一下鏈表是否只有一個節點,如果是,我們釋放完空間以后,將(*pphead)置為空,以免出現野指針的情況。

完善后代碼:

//單鏈表尾刪
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);if ((*pphead)->next== NULL){free(*pphead);*pphead = NULL;}else {SLT* ptail = *pphead;SLT* pcur = *pphead;//遍歷鏈表while (ptail->next){pcur = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;pcur->next = NULL;}
}

測試沒有問題,代碼能夠完成尾刪這個功能。

查找數據

? ? ? ? 查找數據,遍歷鏈表查找數據,如果找到就返回數據所在節點的地址,如果沒有找到就返回NULL;

//查找數據
SLT* SLTFind(SLT* phead, SLType x)
{SLT* ptail = phead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}

這里測試以下:

數據存在時

數據不存在時

指定節點之前插入

? ? ? ? 在鏈表指定節點之前插入數據,我們還需要知道指定節點的前一個節點,所以仍然需要遍歷鏈表,尋找指定節點pos前一個節點。

//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{assert(pphead && *pphead);SLT* ptail = *pphead;if (ptail == pos)//頭插{SLTPushFront(pphead, x);}else{SLT* newnode = SLTCreat(x);while (ptail->next != pos)//找到pos位置{ptail = ptail->next;}newnode->next = pos;ptail->next = newnode;}
}

當然,這里如果故意傳NULL給pos,(這就沒啥意義了)這里也可以寫以下assert斷言pos。

指定節點之后插入

? ? ? ? 在指定節點之后插入數據,就不需要再進行遍歷鏈表,這里直接插入即可。

//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x)
{assert(pos);SLT* newnode = SLTCreat(x);newnode->next = pos->next;pos->next = newnode;
}

刪除指定節點

? ? ? ? 刪除鏈表中的指定節點,我們需要這個節點的上一個節點,所以又需要遍歷鏈表,找到pos上一個節點,修改pos->next指針指向。

代碼如下:

//刪除指定節點
void SLTErase(SLT** pphead, SLT* pos)
{//找到pos上一個節點SLT* ptail = *pphead;while (ptail->next != pos){ptail = ptail->next;}SLT* p = pos->next;free(pos);pos = NULL;ptail->next = p;
}

刪除指定節點后一個節點

? ? ? ? 刪除鏈表指定節點后一個節點,因為pos就是刪除節點的上一個節點,所以不需要遍歷鏈表,直接刪除即可。

//刪除指定位置后一個節點
void SLTEraseAfter(SLT* pos)
{assert(pos->next);SLT* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

這里如果傳過來的就是鏈表的尾節點,那沒辦法刪除后一個節點,所以斷言pos->next;

代碼總覽

#include"SList.h"
//創建節點
SLT* SLTCreat(SLType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}
//輸出鏈表
void SLTPrint(SLT* phead)
{SLT* ptail = phead;while (ptail != NULL)//也可以直接寫成 ptail{printf("%d -> ", ptail->data);ptail = ptail->next;}printf("NULL\n");
}
//單鏈表頭插
void SLTPushFront(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);newnode->next = *pphead;*pphead = newnode;
}
//單鏈表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	assert(pphead);SLT* newnode = SLTCreat(x);if (*pphead == NULL) //判斷鏈表是否為空{*pphead = newnode;}else {SLT* ptail = *pphead;//遍歷鏈表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}
//單鏈表頭刪
void SLTPopFront(SLT** pphead)
{assert(pphead && *pphead);SLT* del = (*pphead);*pphead = (*pphead)->next;free(del);del = NULL;
}
//單鏈表尾刪
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);if ((*pphead)->next== NULL){free(*pphead);*pphead = NULL;}else {SLT* ptail = *pphead;SLT* pcur = *pphead;//遍歷鏈表while (ptail->next){pcur = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;pcur->next = NULL;}
}
//查找數據
SLT* SLTFind(SLT* phead, SLType x)
{SLT* ptail = phead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{assert(pphead && *pphead);SLT* ptail = *pphead;if (ptail == pos)//頭插{SLTPushFront(pphead, x);}else{SLT* newnode = SLTCreat(x);while (ptail->next != pos)//找到pos位置{ptail = ptail->next;}newnode->next = pos;ptail->next = newnode;}
}
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x)
{assert(pos);SLT* newnode = SLTCreat(x);newnode->next = pos->next;pos->next = newnode;
}
//刪除指定節點
void SLTErase(SLT** pphead, SLT* pos)
{//找到pos上一個節點SLT* ptail = *pphead;while (ptail->next != pos){ptail = ptail->next;}SLT* p = pos->next;free(pos);pos = NULL;ptail->next = p;
}
//刪除指定位置后一個節點
void SLTEraseAfter(SLT* pos)
{assert(pos->next);SLT* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

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

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

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

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

相關文章

WGAN(Wassertein GAN)

WGAN E x ~ P g [ log ? ( 1 ? D ( x ) ) ] E x ~ P g [ ? log ? D ( x ) ] \begin{aligned} & \mathbb{E}_{x \sim P_g}[\log (1-D(x))] \\ & \mathbb{E}_{x \sim P_g}[-\log D(x)] \end{aligned} ?Ex~Pg??[log(1?D(x))]Ex~Pg??[?logD(x)]? 原始 GAN …

springboot基于Java的超市進銷存系統+ LW+ PPT+源碼+講解

第三章系統分析與設計 3.1 可行性分析 一個完整的系統,可行性分析是必須要有的,因為他關系到系統生存問題,對開發的意義進行分析,能否通過本網站來補充線下超市進銷存管理模式中的缺限,去解決其中的不足等&#xff0c…

6域名系統DNS

《計算機網絡》第7版,謝希仁 每次記不清楚的知識點,通過上網查找,總是只能看到很零碎的答案。最后還是最喜歡看這個版本的書,一看就回憶起來了,邏輯嚴謹,循循善誘,知識講解的全面又清晰&#xf…

架構師應該在團隊中發揮怎樣的作用?

架構師分為5種: 1.企業架構師EA(Enterprise Architect) EA的職責是決定整個公司的技術路線和技術發展方向。 2.基礎結構架構師IA(Infrastructure Architect) IA的工作就是提煉和優化技術方面積累和沉淀形成的基礎性的、公共的、可復用的框架和組件,這…

Qt 基礎組件速學 鼠標和鍵盤事件

學習目標: 鼠標事件和鍵盤事件應用 前置環境 運行環境:qt creator 4.12 學習內容和效果演示: 1.鼠標事件 根據鼠標的坐標位置,做出對應的事件。 2.鍵盤事件 根據鍵盤的輸入做出對應操作 詳細主要代碼 1.鼠標事件 #include "main…

一文讀懂輕量日志收集系統Loki工作原理

Loki 是由 Grafana Labs 開發的日志聚合系統,設計目標是提供一種高效、低成本的日志收集和查詢解決方案。與傳統的日志系統(如 ELK Stack)不同,Loki 不會對日志內容進行索引,而是僅對日志的元數據進行索引,…

美國大帶寬服務器租用優勢和注意事項

美國大帶寬服務器租用對于需要處理大量數據和提供高速網絡服務的企業至關重要。下面將詳細討論美國大帶寬服務器租用的優勢、適用場景及注意事項,rak部落小編為您整理發布美國大帶寬服務器租用的優勢和注意事項。 優勢 1. 高速數據傳輸: - 大帶寬服務器提…

FTP、http 、tcp

HTTP VS FTP HTTP :HyperText Transfer Protocol 超文本傳輸協議,是基于TCP協議 FTP: File Transfer Protocol 文件傳輸協議, 基于TCP協議, 基于UDP協議的FTP 叫做 TFTP HTTP 協議 通過一個SOCKET連接傳輸依次會話數…

FIND_IN_SET使用案例--[sql語句根據多ids篩選出對應數據]

一 FIND_IN_SET select id,system_ids from intellect_client_info where FIND_IN_SET(5, system_ids) > 0;

Spring Boot 中的監視器是什么?有什么作用?

前言: 監聽器相信熟悉 Spring、Spring Boot 的都知道,但是監視器又是什么?估計很多人一臉懵的狀態,本篇分享一下 Spring Boot 的監視器。 Spring Boot 系列文章傳送門 Spring Boot 啟動流程源碼分析(2) …

Apache DolphinScheduler 與 AWS 的 EMR/Redshift 集成實踐分享

引言 這篇文章將給大家講解關于DolphinScheduler與AWS的EMR和Redshift的集成實踐,通過本文希望大家能更深入地了解AWS智能湖倉架構,以及DolphinScheduler在實際應用中的重要性。 AWS智能湖倉架構 首先,我們來看一下AWS經典的智能湖倉架構圖…

【第20章】MyBatis-Plus邏輯刪除支持

文章目錄 前言一、邏輯刪除的工作原理二、支持的數據類型三、使用方法1.配置全局邏輯刪除屬性2.在實體類中使用 TableLogic 注解 四、常見問題解答1. 如何處理插入操作?2. 刪除接口自動填充功能失效怎么辦? 五、實戰1. 全局配置2. 添加TableLogic3. 自動…

高考選專業,興趣與就業前景該如何平衡?

從高考結束的那一刻開始,有些家長和學生就已經變得焦慮了,因為他們不知道成績出來的時候學生應該如何填報志愿,也不知道選擇什么樣的專業,畢竟大學里面的專業豐富多彩,如何選擇確實是一門學問,而對于學生們…

Oracle的RECYCLEBIN回收站:輕松恢復誤刪對象

目錄 Oracle的RECYCLEBIN回收站:輕松恢復誤刪對象一、概念二、工作原理三、使用方法1 查看回收站中的對象2 恢復回收站中的對象2.1 恢復表(TABLE)2.2 恢復索引(INDEX)2.3 恢復視圖(VIEW)2.4 恢復…

樂清網站建設規劃書

樂清是位于浙江省溫州市的一個縣級市,擁有悠久的歷史和豐富的文化底蘊。隨著互聯網的快速發展,網站建設成為推動樂清經濟和文化發展的重要手段。因此,我們認為有必要制定一個全面的樂清網站建設規劃書,以促進樂清的經濟繁榮和文化…

東芝 TB5128FTG 強大性能的步進電機驅動器

TB5128FTG它以高精度和高效能為設計理念,采用 PWM 斬波方法,并內置時鐘解碼器。通過先進的 BiCD 工藝制造,這款驅動器提供高達 50V 和 5.0A 的輸出額定值,成為廣泛應用場景中的強勁解決方案。 主要特性 TB5128FTG 擁有眾多確保高…

SAP PS學習筆記01 - PS概述,創建Project和WBS

本章開始學習PS(Project System)。 1,PS的概述 PS(Project System)是SAP企業資源規劃系統中的一個關鍵模塊,主要用于項目管理。 它提供了一個全面的框架來規劃、控制和執行項目,涵蓋了從項目啟…

【Express】自定義錯誤碼和通用返回對象

自定義錯誤碼: // 自定義錯誤 const {formatResponse} require("./tool");class ServiceError extends Error {/**** param message 自定義錯誤信息* param code 自定義錯誤碼*/constructor(message, code) {super(message);this.code code;}/*** 將錯…

ZeroMQ最全面試題解讀(3萬字長文)

目錄 解釋ZeroMQ是什么,它的主要用途是什么? ZeroMQ支持哪些通信模式? 描述一下ZeroMQ中的“消息”和“消息幀” 如何在C++中初始化一個ZeroMQ上下文? 在ZeroMQ中,如何創建一個套接字并將其綁定到特定端口? 解釋什么是“管道模式”(Pipe Pattern) 說明如何使用Z…

Spring的三種注入方式的優缺點分析

在 Spring 中,提供了三種依賴注入(也被稱之為 "對象注入","屬性裝配"等)的方式,這篇博客我們來分析一下這三種方式各有哪些優缺點。 一、屬性注入 優點 簡潔,使用方便。 缺點 ? 只…