鏈表(C++)

這是本人第二次學習鏈表,第一次學習鏈表是在大一上的C語言課上,首次接觸,感到有些難;第二次是在大一下學習數據結構時(就是這次),使用C++再次理解鏈表。同時,這也是開啟數據結構學習寫的第一篇文章,但愿以后有時間一直寫下去。

當然,學習數據結構還是保持著學習計算機的基本素養——增、刪、查、改。


一、為什么需要鏈表?

首先,理解數組與鏈表區別,數組是一塊連續的內存空間,有了這塊內存空間,可以通過數組索引計算出任意位置元素內存;而鏈表,不需要一塊連續的內存空間,可以分散在各處,只需通過節點連接起來,這是相對于數組的好處,但是也有弊端,由于鏈表中每個元素不是連續挨著的,所以訪問時,需要從頭結點開始遍歷直至找到你要的元素

二、單鏈表基本操作

首先,創建一條單鏈表:

class ListNode {
public:int val;ListNode* next;ListNode(int x):val(x),next(NULL){}
};ListNode* createLinkedList(std::vector<int> arr) {//輸入數組,轉換成單鏈表if (arr.empty()) {return nullptr;}ListNode* head = new ListNode(arr[0]);ListNode* cur = head;for (int i = 0; i < arr.size(); i++) {cur->next = new ListNode(arr[i]);cur = cur->next;}return head;
}
1、單鏈表查找、遍歷、修改
ListNode* head = createLinkedList({1, 2, 3, 4, 5});
for (ListNode* p = head; p != nullptr; p = p->next) {std::cout << p->val << std::endl;
}

這是遍歷一個單鏈表↑

如果是要通過索引訪問或修改鏈表中的某個節點,也只能用 for 循環從頭結點開始往后找,直到找到索引對應的節點,然后進行訪問或修改。

2、增加

2.1頭插

ListNode* head = createLinkedList({1, 2, 3, 4, 5}); 
ListNode* newHead = new ListNode(6);
newHead->next = head;
head = newHead; // 現在的鏈表 6 -> 1 -> 2 -> 3 -> 4 -> 5

2.2尾插

比頭插只復雜一步,需要遍歷到末尾,再插入

ListNode* head = createLinkedList(std::vector<int>{1, 2, 3, 4, 5});
ListNode* p = head;
while (p->next != nullptr) {p = p->next;
}
p->next = new ListNode(6);// 現在鏈表變成了 1 -> 2 -> 3 -> 4 -> 5 -> 6

2.3中間插入

在鏈表的中間插入,只需要找到前驅節點,然后插入

ListNode* head = createLinkedList({ 1,2,3,4,5 });
ListNode* p = head;
for (int i = 0; i < 2; i++) {p = p->next;
}
ListNode* newNode = new ListNode(66);
newNode->next = p->next;
p->next = newNode;// 現在鏈表變成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
3、刪除

3.1中間刪除

還是找到要刪除的節點的前一個節點,把前一個節點的next指針指向刪除節點的next指針

ListNode* head = createLinkedList({1, 2, 3, 4, 5});
ListNode* p = head;
for (int i = 0; i < 2; i++) {p = p->next;
}
p->next = p->next->next;// 現在鏈表變成了 1 -> 2 -> 3 -> 5

這里不懂可以看看我之前發的鏈表文章(配圖的那篇)鏈表?
3.2尾部刪除

這種刪除是最簡單的,找到倒數第二個節點,將它的next指針設為null

ListNode* head = createLinkedList({1, 2, 3, 4, 5});
ListNode* p = head;
while (p->next->next != nullptr) {p = p->next;
}
p->next = nullptr;// 現在鏈表變成了 1 -> 2 -> 3 -> 4

3.3頭部刪除

ListNode* head = createLinkedList(vector<int>{1, 2, 3, 4, 5});
head = head->next;// 現在鏈表變成了 2 -> 3 -> 4 -> 5

第一次學習鏈表時,這里就出現了困惑,困惑出在第一個節點身上,原第一個節點的next還指向第二個,所以看起來沒有刪除,只是沒有訪問,是否會造成內存泄漏?但實際上,沒有其它引用第一個節點,它就會被回收掉,當然也可以把第一個節點的next設為null,這就避免這個問題,如下:

ListNode* head = createLinkedList(vector<int>{1, 2, 3, 4, 5});
ListNode* oldHead = head;
head = head->next;
oldHead->next = nullptr;
delete oldHead;// 現在鏈表變成了 2 -> 3 -> 4 -> 5

這樣就嚴謹了。

三、雙鏈表基本操作

首先,創建雙鏈表:

class DoublyListNode {
public:int val;DoublyListNode *next, *prev;DoublyListNode(int x) : val(x), next(NULL), prev(NULL) {}
};DoublyListNode* createDoublyLinkedList(vector<int>& arr) {if (arr.empty()) {return NULL;}DoublyListNode* head = new DoublyListNode(arr[0]);DoublyListNode* cur = head;// for 循環迭代創建雙鏈表for (int i = 1; i < arr.size(); i++) {DoublyListNode* newNode = new DoublyListNode(arr[i]);cur->next = newNode;newNode->prev = cur;cur = cur->next;}return head;
}
1、遍歷、查找、修改

對于雙鏈表,從頭節點或尾節點,向后或向前遍歷

DoublyListNode* head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode* tail = nullptr;
// 從頭節點向后遍歷雙鏈表
for (DoublyListNode* p = head; p != nullptr; p = p->next) {cout << p->val << endl;tail = p;
}
// 從尾節點向前遍歷雙鏈表
for (DoublyListNode* p = tail; p != nullptr; p = p->prev) {cout << p->val << endl;
}

訪問或修改節點時,可以根據索引是靠近頭部還是尾部,選擇合適的方向遍歷,這樣可以一定程度上提高效率。


2、增加

2.1頭插
需要改變新節點和原頭結點指針

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* newHead = new DoublyListNode(0);
newHead->next = head;
head->prev = newHead;
head = newHead; // 現在鏈表變成了 0 -> 1 -> 2 -> 3 -> 4 -> 5

頭插步驟如圖

2.2尾插

雙鏈表尾插與單鏈表尾插一樣,需要遍歷到最后一個節點,如果已知尾節點的引用,就簡單很多了(不需要遍歷了)

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* tail = head;
while (tail->next != nullptr) {tail = tail->next;
}
DoublyListNode* newNode = new DoublyListNode(6);
tail->next = newNode;
newNode->prev = tail;
// 更新尾節點引用
tail = newNode;  // 現在鏈表變成了 1 -> 2 -> 3 -> 4 -> 5 -> 6

比較簡單,先是尾節點的next指針指向newNode,然后newNode的prev指針再指向原tail,這是一個互逆過程,即我指向你,你也需要指向我,這樣才符合雙鏈表的定義。

最后,更新一下尾節點,方便下一次在尾部直接插入,重復執行上面的操作。

2.3中間插入

雙鏈表的中間插入需要同時關注前驅指針和后繼指針

如:把元素 66 插入到索引 3(第 4 個節點)的位置

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* p = head;
for (int i = 0; i < 2; i++) {p = p->next;
}
DoublyListNode* newNode = new DoublyListNode(66);
newNode->next = p->next;
newNode->prev = p;p->next->prev = newNode;
p->next = newNode;  // 現在鏈表變成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

下面,用畫圖解釋一下:

第一步,初始化和p的遍歷(遍歷到第三個節點)

第二步,newNode->next = p->next;(紅色箭頭)

第三步,newNode->prev = p;(綠色箭頭)

第四步,p->next->prev = newNode;(藍色)

第四步,p->next = newNode;(黃色)

這樣,66就成功插入到了第三個節點之后


3、刪除

3.1中間刪除

DoublyListNode* head = createDoublyLinkedList(std::vector<int>{1, 2, 3, 4, 5});
// 刪除第 4 個節點
// 先找到第 3 個節點
DoublyListNode* p = head;
for (int i = 0; i < 2; ++i) {p = p->next;
}
// 現在 p 指向第 3 個節點,我們將它后面那個節點摘除出去
DoublyListNode* toDelete = p->next;
// 把 toDelete 從鏈表中摘除
p->next = toDelete->next;
toDelete->next->prev = p;
// 把 toDelete 的前后指針都置為 null 是個好習慣(可選)
toDelete->next = nullptr;
toDelete->prev = nullptr;  // 現在鏈表變成了 1 -> 2 -> 3 -> 5

中間刪除比較復雜,還是采用畫圖的方法理解

第一步,還是初始化和遍歷

第二步,摘出要刪除的節點(即4)

第三步,p->next = toDelete->next;(藍色)

第四步,toDelete->next->prev = p;(黃色)

其實,到這里已經結束了,但還是最開始的問題,為了規范,把要刪除的節點前驅和后繼指針置為null

3.2頭刪

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* toDelete = head;
head = head->next;
head->prev = nullptr;toDelete->next = nullptr;  // 現在鏈表變成了 2 -> 3 -> 4 -> 5

3.3尾刪

在單鏈表中,由于缺乏前驅指針,所以刪除尾節點時需要遍歷到倒數第二個節點,操作它的?next?指針,才能把尾節點摘除出去。但在雙鏈表中,由于每個節點都存儲了前驅節點的指針,所以我們可以直接操作尾節點,把它自己從鏈表中摘除:

DoublyListNode* head = createDoublyLinkedList(std::vector<int>{1, 2, 3, 4, 5});
DoublyListNode* p = head;
while (p->next != nullptr) {p = p->next;
}
// 現在 p 指向尾節點
// 把尾節點從鏈表中摘除
p->prev->next = nullptr;
// 把被刪結點的指針都斷開是個好習慣(可選)
p->prev = nullptr;   // 現在鏈表變成了 1 -> 2 -> 3 -> 4

雙鏈表的頭刪和尾刪比較簡單,所以就沒畫圖

以上就是關于單雙鏈表的基本操作,學識淺薄,錯誤內容還望指正

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

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

相關文章

【SPP】藍牙串口協議應用層深度解析:從連接建立到實戰開發

目錄 一、SPP應用層協議框架與角色模型 1.1 分層協議棧模型 1.2 設備角色模型&#xff08;DevA 與 DevB 交互&#xff09; 二、連接建立流程&#xff1a;從 SDP 到 RFCOMM 2.1 服務發現&#xff08;SDP&#xff09;流程&#xff08;SDP 記錄關鍵參數&#xff09; 2.2 連接…

Giteki 認證:無線產品進入日本市場的關鍵保障

目錄 適用產品認證范圍 認證項目及技術要求 認證流程 認證周期 與其他認證的對比 常見問題 注意事項 Giteki 認證&#xff0c;其名稱來源于日本語 “技適マーク”&#xff0c;羅馬字拼寫為 “GITEKI” &#xff0c;在行業內也常被稱為 Telec 認證、MIC 認證、RF 認證或技…

Ubuntu24.04 配置遠程桌面服務

一&#xff1a;安裝 sudo apt update sudo apt install vino 二&#xff1a;設置 gsettings set org.gnome.Vino require-encryption false # 關閉加密&#xff08;某些 VNC 客戶端不支持加密&#xff09; gsettings set org.gnome.Vino prompt-enabled false # 關閉連接…

人工智能與軟件工程結合的發展趨勢

AI與軟件工程的結合正在深刻改變軟件開發的流程、工具和方法&#xff0c;其發展方向涵蓋了從代碼生成到系統維護的整個生命周期。以下是主要的發展方向和技術趨勢&#xff1a; 1. 軟件架構體系的重構 從“面向過程”到“面向目標”的架構轉型&#xff1a; AI驅動軟件設計以目標…

轉發和重定向的區別詳解

轉發&#xff08;Forward&#xff09;和重定向&#xff08;Redirect&#xff09;是 Web 開發中兩種常用的請求處理方式&#xff0c;主要用于將客戶端請求從一個資源轉移到另一個資源。它們在實現機制、行為表現和應用場景上有顯著區別&#xff0c;以下是對兩者的詳細解析&#…

python專題1-----判斷一個變量是否是字符串類型

在 Python 中&#xff0c;可以使用 isinstance() 函數來判斷一個變量是否是字符串類型。字符串在 Python 中是以 str 類型表示的。下面是一些示例代碼&#xff0c;展示如何判斷一個變量是否是字符串類型&#xff1a; # 示例變量 var1 "Hello, World!" var2 12345 …

軟件工程之需求工程(需求獲取、分析、驗證)

一、需求獲取&#xff08;Requirements Elicitation&#xff09; 1. 定義與目標 需求獲取是通過與用戶、利益相關者等交互&#xff0c;識別并捕獲系統需求的過程&#xff0c;目標是明確用戶意圖與業務目標&#xff0c;避免后期因需求偏差導致返工。 2. 主要方法 問卷法&…

Java簡單生成pdf

生成這樣的PDF 直接上代碼 public static void main(String[] args) {String logoPath "Q:\\IdeaWork\\Demo\\src\\main\\webapp\\images\\logo.jpg"; // 替換為實際路徑String baseDir "E:/Demo/TEST/problem/Generate"; // 基礎目錄int year 2025; //…

k8s存儲介紹(六)StorangeClass

一、Kubernetes 存儲類&#xff08;StorageClass&#xff09;詳解 1. 什么是 StorageClass&#xff1f; 在 Kubernetes 中&#xff0c;StorageClass&#xff08;存儲類&#xff09;是一種用于動態創建 PersistentVolume&#xff08;PV&#xff09;的資源對象。它允許管理員根…

C++:allocator類(動態數組續)

1.為什么需要 allocator&#xff1f; 在 C 中&#xff0c;動態內存管理通常通過 new 和 delete 完成&#xff1a; int* p new int; // 分配內存 構造對象 delete p; // 析構對象 釋放內存 但 new 和 delete 有兩個問題&#xff1a; 耦合性&#xff1a;將內…

北斗導航 | 中國北斗衛星導航系統的發展歷程——“三步走”戰略:背景,信號頻點,調制方式,短報文,等

中國北斗衛星導航系統的發展歷程按照“三步走”戰略逐步推進,從區域服務到全球覆蓋,形成了北斗一號、北斗二號、北斗三號三代系統的迭代升級,展現了中國航天科技的自主創新與突破。以下是各階段的核心內容與發展特點綜述:一、北斗一號:中國衛星導航的奠基(1994-2003年) …

Headless Chrome 優化:減少內存占用與提速技巧

在當今數據驅動的時代&#xff0c;爬蟲技術在各行各業扮演著重要角色。傳統的爬蟲方法往往因為界面渲染和資源消耗過高而無法滿足大規模數據采集的需求。本文將深度剖析 Headless Chrome 的優化方案&#xff0c;重點探討如何利用代理 IP、Cookie 和 User-Agent 設置實現內存占用…

英偉達GB300新寵:新型LPDDR5X SOCAMM內存

隨著人工智能&#xff08;AI&#xff09;、機器學習&#xff08;ML&#xff09;和高性能計算&#xff08;HPC&#xff09;應用的快速發展&#xff0c;對于高效能、大容量且低延遲內存的需求日益增長。NVIDIA在其GB系列GPU中引入了不同的內存模塊設計&#xff0c;以滿足這些嚴格…

靜態網頁應用開發環境搭建實戰教程

1. 前言 靜態網頁開發是前端工程師的基礎技能之一&#xff0c;無論是個人博客、企業官網還是簡單的Web應用&#xff0c;都離不開HTML、CSS和JavaScript。搭建一個高效的開發環境&#xff0c;能夠極大提升開發效率&#xff0c;減少重復工作&#xff0c;并優化調試體驗。 本教程…

Python每日一題(9)

Python每日一題 2025.3.29 一、題目二、分析三、源代碼四、deepseek答案五、源代碼與ai分析 一、題目 question["""企業發放的獎金根據利潤提成。利潤(I)低于或等于10萬元時,獎金可提10%,利潤高于10萬元,低于20萬元時,低于10萬元的部分按10%提成,高于10萬元的部…

游戲引擎學習第187天

看起來觀眾解決了上次的bug 昨天遇到了一個相對困難的bug&#xff0c;可以說它相當棘手。剛開始的時候&#xff0c;沒有立刻想到什么合適的解決辦法&#xff0c;所以今天得從頭開始&#xff0c;逐步驗證之前的假設&#xff0c;收集足夠的信息&#xff0c;逐一排查可能的原因&a…

【入門初級篇】布局類組件的使用(1)

【入門初級篇】布局類組件的使用&#xff08;1&#xff09; 視頻要點 &#xff08;1&#xff09;章節大綱介紹 &#xff08;2&#xff09;布局類組件類型介紹&#xff1a;行布局、列布局、標題 &#xff08;3&#xff09;實操演示&#xff1a;列表統計查詢布局模型 點擊訪問my…

對內核fork進程中寫時復制的理解記錄

前言 文章寫于學習Redis時對aof后臺重寫中寫時復制的疑問 一、感到不理解的歧義 在部分技術文檔中&#xff08;以小林的文章為例&#xff09;&#xff0c;對寫時復制后的內存權限存在如歧義&#xff1a; ! 二、正確技術表述 根據Linux內核實現&#xff08;5.15版本&#x…

Ditto-Talkinghead:阿里巴巴數字人技術新突破 [特殊字符]?

Ditto-Talkinghead&#xff1a;阿里巴巴數字人技術新突破 &#x1f5e3;? 阿里巴巴推出了一項新的數字人技術&#xff0c;名為 Ditto-Talkinghead。這項技術主要用于生成由音頻驅動的說話頭&#xff0c;也就是我們常說的“數字人”。不過&#xff0c;現有的基于擴散模型的同類…

.NET開發基礎知識1-10

1. 依賴注入&#xff08;Dependency Injection&#xff09; 技術知識&#xff1a;依賴注入是一種設計模式&#xff0c;它允許將對象的依賴關系從對象本身中分離出來&#xff0c;通過構造函數、屬性或方法參數等方式注入到對象中。這樣可以提高代碼的可測試性、可維護性和可擴展…