數據結構篇:線性表的另一表達—鏈表之單鏈表(下篇)

目錄

1.前言

2.是否使用二級指針

3.插入/刪除

? ? ? ? 3.1 pos位置前/后插入

3.2 查找函數

3.3 pos位置刪除

3.4 pos位置后面刪除

3.5 函數的銷毀?

4.斷言問題

? ? ? ? 4.1 斷言pphead

? ? ? ? 4.2 斷言*pphead

5.三個文件的代碼?

5.1 頭文件

5.2 具體函數實現

5.3 測試用例


1.前言

????????之前是講述了單鏈表的頭插尾插,頭刪尾刪函數部分,并且揭示了鏈表相對于順序表的優勢性。更重要的是對于next的理解和對二級指針的理解,為什么在單鏈表中用一級指針報錯而要用二級指針,其實簡單的說就是看會不會改變指針指向

2.是否使用二級指針

? ? ? ? 在單鏈表中,無論是尾插還是尾刪。尾插的時候,在第一次插入元素的時候,沒有節點,頭節點是一個指向為空。實參那里一開始的鏈表初始化為空,是一個NULL。要尾插的時候,通過函數來改變外部的這個phead的指向,這個實參plist是一個一級指針,如果要通過函數來改變這個一級指針的指向,就是要傳二級指針,也就是一級指針的地址

????????頭插的時候,傳的是二級指針,因為和尾插一樣,每次頭插都要改變頭節點的指向。

????????尾刪的時候,刪到最后一個節點,它就會改變頭節點的指向,仔細想,刪除最后一個節點刪沒了,頭節點這個時候指向應該指向空,所以也是要二級指針才能改變。


? ? ? ? 結論就是看這個頭節點會不會變,如果會變的話,就用二級指針,如果不變,那就不用傳二級指針。

3.插入/刪除

? ? ? ? 3.1 pos位置前/后插入

? ? ? ? 和順序表一樣?,鏈表也是可以在中間某個位置pos前/后插入的,而且在特殊位置,比如在第一個位置前插入,那等同于頭插。那么就可以把頭插的函數復用就可以了。思路是:定義一個前指針這里用former,找到pos的前一個位置,怎么找呢,有next這個節點,只要former的next不是pos的地址,就一直遍歷,直至找到。然后former的next指向新節點,新節點的next再指向pos。(相當于把former和pos之間的指針斷開了,重新進行了指向),這是pos位置前插入的思路圖:

pos位置前插入

測試用例如下:找到3的位置,然后在3的位置前插入7

測試用例

?????????當然也可以有頭插的效果,在判斷該位置是否是頭節點后,如果是,就復用頭插函數

void SLTinsert(SLTnode** pphead,SLTnode*pos, Sltdatatype x)
{assert(pos);assert(pphead);//如果pos是第一個,就等于頭插if (pos == *pphead){//復用頭插函數SLTpushfront(pphead,x);}
}

????????測試當然也是成功的:

? ? ? ? pos位置后面插入,也是同理,先查找,是否有該位置存在,有就給出一個返回值。然后進行插入,那么既然在第一個位置前插入數據等同于頭插,那在最后一個位置后插入,是什么,應該是尾插吧。

????????思路就是找到pos后,先用一個next的指針保存原來pos的next,然后pos的next指向newnode,也就是新節點,最后新節點的next再指向保存原來pos的next的那個next指針。有點小繞,但畫個圖就明白了。保存是為了更方便點。這是順序的從左到右依次連接。

? ? ? ? 或者不那么繞,前者是pos->newnode->pos的下一個,形成了中間插入。也可以newnode->posd的next,pos->newnode。從后往前連接,就可以不用定義指針去保存pos的下一個節點。代碼會少一兩行。這里我選用第二種方法

void SLTinsert_afterpos(SLTnode** pphead,SLTnode *pos, Sltdatatype x)
{assert(pphead);assert(pos);SLTnode* newnode = insertSLTnode(x);newnode->next = pos->next;pos->next = newnode;
}

3.2 查找函數

? ? ? ? 在上文中,我們要在pos位置插入,和順序表一樣,也是需要先找到該位置才能進行插入,需要有個返回值,然后再調用插入函數。而查找函數思路也很簡單,只需要遍歷鏈表,找到是否有data等于x的值就可以,沒有就返回NULL。

SLTnode* SLTfind(SLTnode* pphead, Sltdatatype x)
{//遍歷SLTnode* cur = pphead;while (cur){if (cur->data==x){return cur;}cur = cur->next;}return NULL;
}

3.3 pos位置刪除

? ? ? ? 經過pos位置的前后插入練習,其實pos位置的刪除,pos位置后的刪除思路也是很明確了。正確掌握好鏈表的位置關系,以及對next的理解,就基本沒什么問題。刪除pos位置,也就是意味著要把pos的前后連起來,并且釋放掉pos位置。那么就需要有一個former指針,該指針的next指向pos的next,就可以了。思路圖如下:

????????特殊情況:如果要刪除的位置是頭節點,也就是頭刪,同樣復用頭刪函數。

void SLTErase(SLTnode** pphead, SLTnode* pos)
{assert(pphead);assert(*pphead);if (pos == *pphead){SLTpopfront(pphead);}else{SLTnode* former = *pphead;while (former->next != pos){former = former->next;}former->next = pos->next;free(pos);}
}

3.4 pos位置后面刪除

????????緊接著來看pos位置后刪除,這里同pos位置后插入一樣,有兩種寫法,一種是pos的next指向pos的next的next,就是跳過了中間的數據,因為中間的數據是要被刪除的。最后釋放掉pos后面的位置就行。


或者用一個有意義的名字定義個指針,該指針把要刪除的數據保存下來,pos的next和該指針的下一個連接起來就可以。兩者其實沒有本質區別的。?

void SLTerase_afterpos(SLTnode** pphead, SLTnode* pos)
{assert(pos->next);SLTnode* erase = pos->next;pos->next = pos->next->next;free(erase);erase = NULL;}

?之前的案例是在3的后面插入一個7,那么這次把7刪掉,就是可以用到pos后面刪除數據。

3.5 函數的銷毀?

? ? ? ? 由于我們申請的空間都是malloc出來,在堆上申請的,那根據malloc的特質,我們需要需要把它銷毀掉并且置空,防止內存泄露的問題出現。

????????銷毀的思路就是,把鏈表存放到一個指針中(cur)(這里不需要改變指針的指向,只是改變結構體的內容,所以只需要一級指針),用另一個指針next去保存當前節點的下一個,先釋放保存鏈表數據的cur指針,然后指針內容更新為節點的下一個,循環到鏈表為空。這里沒有進行置空,是因為函數的形參不改變實參,所以里面置空不影響外部函數鏈表的結果,一級指針改變不了一級指針的內容,如果要在里面更改就要傳遞二級指針。所以我傳的是一級指針,在外部置空。

void SLTdestory(SLTnode * phead)
{SLTnode* cur = phead;while (cur){SLTnode* next = cur->next;free(cur);cur=next;}
}

4.斷言問題

? ? ? ? 在寫單鏈表的過程中,每個函數是否要斷言,斷言哪些地方,是需要考慮的,不能一刀切,結合實際情況實際考慮的

? ? ? ? 4.1 斷言pphead

????????首先斷言的情況是pphead傳入的是鏈表地址,鏈表的地址傳錯了的話是NULL會發生報錯,那么我們要斷言的情況,就是防止有空的誤傳進來(比如沒有取地址)。所以鏈表地址一定不為空。

? ? ? ? 4.2 斷言*pphead

? ? ? ? *pphead,是鏈表的內容,看*pphead有沒有可能為空,如果這個值在某個情況下不應該是空,那么我們就可以斷言,因為不可能為空的,一定有數值的傳進來,結果你斷言后報錯了,說明傳進來的有問題啊。如果這個函數允許有空的存在,那么不需要斷言,因為斷言了不就傳不進來,就與初衷有悖。


????????舉個例子,插入的時候,*pphead有沒有可能為空,空鏈表能插入,說明可以為空,為空你加assert(*pphead),那不是鐵鐵報錯嗎。空鏈表能打印,能插入,不用斷言空鏈表不能尾刪,不能頭刪,要斷言,為空還刪什么。但是pphead要斷言,它是鏈表的地址,它一定鐵定不為空。所以一定要*pphead斷言

????????結論是:??所以有個思維就是如果這個值絕對不為空,那么我們就可以斷言。而且斷言會直接報錯,告訴你錯在哪里,根據場景去靈活變通,發現基本錯誤,調試的成本,花費的時間要遠比斷言來的高。在這里pphead一定不為空,*pphead看場景。具體情況具體分析。

5.三個文件的代碼?

? ? ? ? 這里展示頭文件,具體函數和測試用例的全部代碼。

5.1 頭文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int Sltdatatype;
typedef struct SListnode
{Sltdatatype data;struct SListnode* next;
}SLTnode;
void SLTprint(SLTnode *phead);
SLTnode* insertSLTnode(Sltdatatype x);
void SLTpushback(SLTnode** pphead, Sltdatatype x);
void SLTpopback(SLTnode** pphead);
void SLTpushfront(SLTnode** pphead, Sltdatatype x);
void SLTpopfront(SLTnode** pphead);
SLTnode* SLTfind(SLTnode* pphead, Sltdatatype x);
//pos之前插入
void SLTinsert(SLTnode **pphead,SLTnode*pos,Sltdatatype x);
//pos位置刪除
void SLTErase(SLTnode** pphead, SLTnode* pos);
//pos后面插入
void SLTinsert_afterpos(SLTnode**phead,SLTnode* pos,Sltdatatype x);
//pos后面刪除
void SLTerase_afterpos(SLTnode** pphead, SLTnode* pos);
//鏈表銷毀
void SLTdestory(plist);

5.2 具體函數實現

#define _CRT_SECURE_NO_WARNINGS 1
#include"Slist.h"
SLTnode* insertSLTnode(Sltdatatype x)
{//申請一塊空間給新的節點SLTnode* newnode = (SLTnode*)malloc(sizeof(SLTnode));if (newnode == NULL){perror("fail malloc");return 0;}newnode->data = x;newnode->next = NULL;return newnode;
}
//尾插
void SLTpushback(SLTnode **pphead,Sltdatatype x)
{assert(pphead);SLTnode* newnode = insertSLTnode(x);if (*pphead==NULL){*pphead = newnode;}//找尾else{//定義尾變量SLTnode* tail = *pphead;while (tail->next!=NULL){tail = tail->next;}//tail->next這個指針指向新節點tail->next= newnode;}
}//尾刪
void SLTpopback(SLTnode** pphead)
{//檢查assert(pphead);assert(*pphead);//一個節點if ((*pphead)->next==NULL){free(*pphead);*pphead = NULL;}//多個節點else{// 找尾SLTnode* former = NULL;SLTnode* tail = *pphead;while (tail->next != NULL){former = tail;tail = tail->next;}free(tail);tail = NULL;former->next = NULL;}
}
void SLTprint(SLTnode* phead)
{SLTnode* cur = phead;while (cur){printf("%d ",cur->data);cur=cur->next;}printf("NULL\n");
}//頭插
void SLTpushfront(SLTnode** pphead, Sltdatatype x)
{assert(pphead);SLTnode* newnode = insertSLTnode(x);//把newnode的下一個給pilstnewnode->next = *pphead;//鏈表指針指向新節點,變成新的頭部*pphead =newnode;
}
//頭刪
void SLTpopfront(SLTnode** pphead)
{assert(pphead);assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//第一個節點的地址,給到first這個指針SLTnode* first = *pphead;//plist指向first的下一個地址,就是第二個節點*pphead = first->next;free(first);//刪除第一個first = NULL;
}
SLTnode* SLTfind(SLTnode* pphead, Sltdatatype x)
{//遍歷SLTnode* cur = pphead;while (cur){if (cur->data==x){return cur;}cur = cur->next;}return NULL;
}
void SLTinsert(SLTnode** pphead,SLTnode*pos, Sltdatatype x)
{assert(pos);assert(pphead);//如果pos是第一個,就等于頭插if (pos == *pphead){//復用頭插函數SLTpushfront(pphead,x);}else{SLTnode* former = *pphead;if (former->next!=pos){former = former->next;}SLTnode* newnode = insertSLTnode(x);former->next = newnode;newnode->next = pos;}
}
void SLTErase(SLTnode** pphead, SLTnode* pos)
{assert(pphead);assert(*pphead);if (pos == *pphead){SLTpopfront(pphead);}else{SLTnode* former = *pphead;while (former->next != pos){former = former->next;}former->next = pos->next;free(pos);}}
void SLTinsert_afterpos(SLTnode** pphead,SLTnode *pos, Sltdatatype x)
{assert(pphead);assert(pos);SLTnode* newnode = insertSLTnode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTerase_afterpos(SLTnode** pphead, SLTnode* pos)
{assert(pos->next);SLTnode* erase = pos->next;
pos->next = pos->next->next;free(erase);erase = NULL;}void SLTdestory(SLTnode * phead)
{SLTnode* cur = phead;while (cur){SLTnode* next = cur->next;free(cur);cur=next;}
}

5.3 測試用例


void test7()
{SLTnode* plist = NULL;SLTpushback(&plist, 1);SLTpushback(&plist, 2);SLTpushback(&plist, 3);SLTpushback(&plist, 4);SLTpushback(&plist, 5);SLTprint(plist);SLTnode* ret = SLTfind(plist, 3);SLTinsert_afterpos(&plist,ret,7);SLTprint(plist);SLTerase_afterpos(&plist, ret);SLTprint(plist);SLTdestory(plist);plist = NULL;
}
int main()
{test7();return 0;
}

? ? ? ? 單鏈表的復習和講解就到這里了?,單鏈表一開始學習還是比較晦澀難懂的,但是寫多了,錯誤多了,坑踩多了,就會熟悉里面的內容了。還是要多理解,多做題,后面也會講解一些單鏈表的O經典J題來加強印象。多做題也是熟悉鏈表的一個很好的方式!

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

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

相關文章

完美解決react-native文件直傳阿里云oss問題一

前言 通常情況下&#xff0c;作為前后端分離的項目來說&#xff0c;文件上傳是最尋常的功能之一。雖然每個公司選擇的文件管理云庫各不相同&#xff0c;但實現思路基本一致。我所在公司使用阿里云oss文件管理&#xff0c;之前服務端做了透傳&#xff0c;但是由于每個測試環境的…

5.運輸層

5. 運輸層 1. 概述 第2~4章依次介紹了計算機網絡體系結構中的物理層、數據鏈路層和網絡層&#xff0c;它們共同解決了將主機通過異構網絡互聯起來所面臨的問題&#xff0c;實現了主機到主機的通信然而在計算機網絡中實際進行通信的真正實體&#xff0c;是位于通信兩端主機中的…

告別手動時代!物聯網軟件開發讓萬物自動互聯

清晨&#xff0c;智能窗簾隨著陽光自動拉開&#xff1b;運動時&#xff0c;手表精準記錄著健康數據&#xff1b;回到家&#xff0c;室溫早已調節至最舒適狀態...這些場景的實現&#xff0c;都離不開物聯網軟件開發的技術支撐。在智能家居軟件開發、智能穿戴軟件開發、醫療器械軟…

Fiori學習專題十二:Shell Control as Container

為了讓我們的app更加適應不同的設備&#xff0c;這節課我們引入shell控件作為根元素 1.修改App.view.xml&#xff0c;加入Shell控件 <mvc:ViewcontrollerName"ui5.walkthrough.controller.App"xmlns"sap.m"xmlns:mvc"sap.ui.core.mvc"displa…

AI 與高性能計算的深度融合:開啟科技新紀元

在當今科技迅猛發展的時代&#xff0c;人工智能&#xff08;AI&#xff09;與高性能計算&#xff08;HPC&#xff09;正以前所未有的態勢深度融合&#xff0c;這種融合宛如一場強大的風暴&#xff0c;席卷并重塑著眾多領域的格局。從科學研究的突破到商業應用的革新&#xff0c…

「Unity3D」TextMeshPro使用TMP_InputField實現,輸入框高度自動擴展與收縮

先看實現效果&#xff1a; 要實現這個效果&#xff0c;有三個方面的問題需要解決&#xff1a; 第一&#xff0c;輸入框的高度擴展&#xff0c;內部子元素會隨著錨點&#xff0c;拉伸變形——要解決這個問題&#xff0c;需要將內部元素改變父類&#xff0c;然后增加父類高度&am…

多模態大語言模型arxiv論文略讀(四十七)

AdaShield: Safeguarding Multimodal Large Language Models from Structure-based Attack via Adaptive Shield Prompting ?? 論文標題&#xff1a;AdaShield: Safeguarding Multimodal Large Language Models from Structure-based Attack via Adaptive Shield Prompting …

美的人形機器人即將投入實際應用

國內家電巨頭美的集團近日公布了其自主研發的人形機器人的具體落地計劃。根據公司披露的信息&#xff0c;這款機器人將于5月在湖北荊州的洗衣機工廠率先投入使用&#xff0c;承擔設備運維、質量檢測和物料搬運等工作任務。預計今年下半年&#xff0c;該機器人還將進入美的線下門…

從零開始搭建你的個人博客:使用 GitHub Pages 免費部署靜態網站

&#x1f310; 從零開始搭建你的個人博客&#xff1a;使用 GitHub Pages 免費部署靜態網站 在互聯網時代&#xff0c;擁有一個屬于自己的網站不僅是一種展示方式&#xff0c;更是一種技術能力的體現。今天我們將一步步學習如何通過 GitHub Pages 搭建一個免費的個人博客或簡歷…

canvas動畫:點隨機運動 距離內自動連接成線 鼠標移動自動吸附附近的點

思路/實現步驟 創建canvas元素 獲取canvas的上下文ctx 初始化點的信息&#xff08;數量、初始坐標、移動方向、移動速度、大小、顏色&#xff09; 繪制點 繪制點之間的連線 點有規律的動起來 動畫循環 鼠標移動相關邏輯 點鼠標之間連線 鼠標吸附邏輯 添加配置項 重繪…

2025年4月AI科技領域周報(4.21-4.27):大模型生態加速演進 通用AI開啟產業融合新范式

2025年4月AI科技領域周報&#xff08;4.21-4.27&#xff09;&#xff1a;大模型生態加速演進 通用AI開啟產業融合新范式 一、本周熱點回顧1. OpenAI發布全球首款通用AI芯片「GPT-NPU」 重塑AI算力架構2. 谷歌DeepMind發布醫療AI系統「Med-Pathfinder」 實現全科室智能診斷3. 微…

【Bash】可以請您解釋性地說明一下“2>1”這個語法嗎?

Qwen3-235B 當然可以&#xff01;我們一步步拆解 2>&1 的含義&#xff0c;用生活化的例子來理解它&#xff1a; &#x1f9e0; 前提知識&#xff1a;三個“水流通道” 在 Linux/Unix 系統中&#xff0c;程序運行時有三種默認的“水流通道”&#xff1a; 標準輸入&…

MAC如何安裝多版本jdk(以8,11,17為例)

MAC如何安裝多版本jdk(以8,11,17為例&#xff09;_mac安裝jdk17-CSDN博客

Nginx核心

一、概述 Nginx一個具有高性能的【HTTP】和【反向代理】的【WEB服務器】&#xff0c;同時也是一個電子郵件代理服務器。正向代理服務的是客戶端&#xff08;比如VPN&#xff09;&#xff0c;反向代理服務的是服務端。Nginx是多進程的&#xff0c;有一個Master進程控制多個Worke…

綜合開發-手機APP遠程控制PLC1500柱燈的亮滅

要通過 ??Unity3D?? 開發的手機 App 控制 ??電氣柜上面的柱燈&#xff0c;需要WIFI模塊作為橋梁&#xff0c;按照以下步驟實現&#xff1a; ??1. 硬件準備&#xff08;硬件部分&#xff09;?? ??所需材料?? ??ESP32開發板??&#xff08;如ESP32-WROOM-32&a…

五款提效工具

1. 億可達 核心功能&#xff1a;通過“觸發器動作”模式&#xff0c;實現任務自動執行&#xff08;如郵件轉發、評論回復、數據同步&#xff09;。 適用場景&#xff1a;自動同步Notion項目到滴答清單生成待辦事項 優勢&#xff1a;節省重復操作時間&#xff0c;減少人為錯誤&a…

Docker化HBase排錯實錄:從Master hflush啟動失敗到Snappy算法未支持解決

前言 在容器化時代&#xff0c;使用 Docker 部署像 HBase 這樣復雜的分布式系統也比較方便。社區也提供了許多方便的 HBase Docker 鏡像&#xff0c;沒有找到官方的 apache的&#xff0c;但有包含許多大數據工具的 harisekhon/hbase 或用于學習目的的 bigdatauniversity/hbase…

windows遠程服務器數據庫的搭建和遠程訪問(Mysql忘記密碼通過Navicat連接記錄解密密碼)

服務器數據庫的搭建和遠程訪問 mysql數據庫安裝&#xff08;詳細&#xff09; window安裝mysql詳細流程 路程&#xff1a;重設MySQL5密碼&#xff0c;發現遠程服務器原本有一個MySQL5&#xff0c;嘗試在服務器本地建立連接被拒絕&#xff0c;因為不知道密碼。 &#xff08;1…

每日c/c++題 備戰藍橋杯(P1093 [NOIP 2007 普及組] 獎學金)

洛谷P1093 [NOIP 2007 普及組] 獎學金 詳解題解 題目背景與要求 題目鏈接&#xff1a;P1093 獎學金 核心任務&#xff1a;根據學生三科總分評選前5名獎學金獲得者&#xff0c;需按特定規則排序輸出。 排序規則&#xff08;按優先級從高到低&#xff09;&#xff1a; 總分降…

openEuler 22.03 安裝 Nginx,支持離線安裝

目錄 一、環境檢查1.1 必要環境檢查1.2 在線安裝&#xff08;有網絡&#xff09;1.3 離線安裝&#xff08;無網絡&#xff09; 二、下載Nginx2.1 在線下載2.2 離線下載 三、安裝Nginx四、開機自啟服務五、開放防火墻端口六、常用命令 一、環境檢查 1.1 必要環境檢查 # 查看 g…