数据结构-线性表之单链表
文章目錄
- 一:相關概念
- (1)什么是鏈表
- (2)鏈表的優點和缺點
- (3)鏈表的分類
- 二:實踐
- (1)準備工作
- (2)結構體定義
- (3)操作
- A:創造頭結點(初始化)
- ①:頭結點?頭指針?
- ②:初始化
- B:打印
- C:單鏈表尾插
- ①:如何尾插?
- ②:尾插代碼第一種
- ②:尾插代碼第二種
- D:單鏈表尾刪
- E:單鏈表頭插
- ①:如何頭插?
- ②:頭插代碼
- F:單鏈表頭刪
- ①:如何頭刪?
- ②:頭刪代碼
- G:單鏈表查找
- H:單鏈表任意位置插入
- I:單鏈表任意位置刪除
- 三:全部代碼
一:相關概念
(1)什么是鏈表
官方定義:鏈表是一種物理存儲結構上非連續的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的
用一張圖表示如下:
可以這樣描述:結點散落的分布在內存區域中,每個結點地址保存在前一個結點的指針域中,同時該結點的指針域又保存下一個結點的地址。
指向第一個結點的指針叫做頭指針
(2)鏈表的優點和缺點
- 鏈表的優點是相對于順序表而言的,由于順序表一次開辟一大片空間,而且增容時也會造成一定空間的浪費。所以鏈表可以按需索取,同時其插入和刪除也十分方便
- 當然鏈表也是有缺點的,最大的缺點在于它不能像順序表那樣隨機訪問,就拿最普通的單鏈表而言,要想找到它最后一個結點,那么必須完整訪問前n-1個結點,類似于按圖索驥。
(3)鏈表的分類
鏈表的分類很多,帶頭結點,不帶頭結點,單鏈表,雙鏈表等等。但是我們最常用的就是以下兩種
二:實踐
(1)準備工作
和前面一樣,也是三個文件
(2)結構體定義
對比順序表,順序表就像是許多相同的結構體,依次排列,雖然這樣說不準確,但是有助于理解。那么對于鏈表來說,可以比喻為把順序表中的每個結點“打散”,分散于內存中,以前對于順序表來說,整個結點全放數據就很OK,那是因為下一個數據一定在該結點后面,不用擔心找不見。但是對于鏈表來說,把它“打散”之后,就存在一個問題,如何去找下一個元素?就像一條鏈子,其中一環斷了,整個也就斷了。也就是說,對于鏈表,它的結點內不能只存放數據,還必須騰出一定空間來放一個指針,這個指針指向下一個結點
通過上述描述,可以定義單鏈表中每個結點的結構體如下
(3)操作
A:創造頭結點(初始化)
①:頭結點?頭指針?
一個單鏈表一定會有一個頭指針,頭指針一定指向第一個結點,如果它指向的第一個結點沒有存放數據(這個結點也經常叫做哨兵結點),這樣的鏈表稱為帶頭結點的鏈表,相反如果它指向的第一個結點存放了數據,這樣的鏈表稱為不帶頭結點的鏈表(不帶頭結點的鏈表是oj題的“常客”)
這兩種鏈表在插入第一個結點時情況稍有不同:假設頭結點已經申請好,此時要插入第一個結點NewNode
head=NewNode//不帶頭結點 head->next=NewNode//帶頭結點可以發現,帶頭結點鏈表雖然浪費了一個空間,但是它把許多操作都歸一化了,使得第一個結點不那么特殊,也就是不需要特殊處理
但是oj題給你永遠都是不帶頭結點的鏈表。
②:初始化
初始化時,按需索取,開始只有一個頭結點
B:打印
C:單鏈表尾插
①:如何尾插?
順序表由于可以隨機訪問,所以把數組傳過去,然后直接找到末尾元素即可。那么對于鏈表就有點不同了,我能傳過去的只是第一個結點,由第一個結點可以知道第二個結點,由第二個結點可以知道第三個幾點····依次類推。所以我要尾插時**,就必須找到尾結點**,那么怎么就知道它是尾節點呢?其實也很容易,尾節點它是最后一個結點,那么它的next肯定為NULL,所以我只需將第一個結點傳過去,然后創建一個循環指針(一定要創建一個循環指針,不能直接拿上第一個節點的指針循環,這樣頭指針變了,整個鏈表丟了),從第一個結點的位置開始循環訪問,直到你所指向的這個結點的next分量為NULL時,這個結點就是尾結點。
②:尾插代碼第一種
根據以上的分析,我們似乎可以寫出下面的代碼
但是運行之后,卻什么都沒有
這是為什么呢?我們通過調試可以一探究竟
那這個問題就是我前面說過的不帶頭結點鏈表和帶頭結點鏈表的問題。這里我們申請的是不帶頭結點的鏈表,head指針開始時NULL,因為鏈表為空,它沒有結點可以指向。所以在進入尾插函數,用把head賦值給了tail,tail也為空,也就不能進行“->”這樣的操作。
那么你可能會想,那直接把newNode的值賦值給tail就可以了,但是需要明白,tail確實此時會指向newNode,但是head還處于游離狀態,如果不能把head指向第一個結點,那么這個鏈表等于沒有構造,因為“火車頭”都按不上,還能叫火車嗎?
所以這里就必須分為兩種情況,這就是不帶頭結點的鏈表的一個小弊端,也就體現在插入第一個結點時的這種尷尬情況。所以改寫如下
但是運行之后,依舊沒有東西
仍然通過調試,一探究竟
可以發現,進入尾插函數后,把NeNode值賦值給了Phead,但是head依舊沒變,所以就不可能打印。
所以這也是大多數人最容易犯錯的地方,因為我們對于指針的第一映像,總感覺它是萬能的,但是我們沒有意識到一點是,指針為什么能發揮作用?這是因為指針的解引用操作。
但凡是學過C語言的人,在接觸指針這一節時,絕對會知道一個關于指針交換變量的值的例子。如果傳值就不能交換,而為什么傳值,或者是什么情況下叫做傳值,這個問題是很多人沒有想過的。這里我的體會是,調用函數時傳入的變量的類型和形參接受的變量的類型一致時這就是傳值,所以我們上述尾插時,傳入尾插函數的是一級指針變量,被調函數形參也用一級指針接受,所以一旦涉及到賦值操作,也就是上面的phead=NewNode,就不可能成功,相反咋們在else語句中寫的那個卻是正確的,因為此時我們拿到一級指針沒有用它來賦值,而是進行解引用,也就是指針的本職工作。但是為什么最后依舊沒有輸出呢,就是因為第一次這個特殊情況,head沒有被修改,自然而然整個隊列都找不見。
經過以上分析,改寫如下
(這里,終于成功了!)
②:尾插代碼第二種
上述代碼實現了尾插。但是有什么缺陷呢?觀察尾插函數可以知道,其中有一個關鍵步驟,就是尋找尾節點,雖然計算機對于比較這樣的一個操作執行起了不會費太大力氣,但是這種寫法總讓我們感覺有點“傻”,總感覺是在做無用功一樣。我們能不能想一種方法,定位它的尾節點,使其在進入尾插函數后,不用比較,直接根據記錄進行插入。
D:單鏈表尾刪
對于單鏈表來說,初學者最容易犯錯的就是尾插處的二級指針問題,只要把尾插明白了,尾刪還是很容易的。
在尾刪時要考慮三種情況,分別是單鏈表尾空,單鏈表內只有一個元素和正常情況
對于上面的第三題刪除邏輯如下
E:單鏈表頭插
①:如何頭插?
頭插的邏輯如下
②:頭插代碼
F:單鏈表頭刪
①:如何頭刪?
頭刪時注意討論情況(這種刪法粗暴直接,但是容易出現內存泄漏的問題)
頭刪邏輯如下
②:頭刪代碼
G:單鏈表查找
H:單鏈表任意位置插入
在某位置插入時,首先得到該位置的地址,所以要調用上述查找函數
邏輯如下
代碼如下
I:單鏈表任意位置刪除
刪除邏輯如下
代碼如下
三:全部代碼
頭文件
#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> typedef int SlistDataType;//結點類型定義 typedef struct SlistNode {SlistDataType data;//數據域struct SlistNode* next;//指向下一個結點的指針 }SlNode;void SlistPushBack(SlNode** head,SlNode** tail, SlistDataType x);//尾插 //void SlistPushBack(SlNode** phead, SlistDataType x);//尾插 void SlistPopBack(SlNode** phead);//尾刪 void SlistPushFront(SlNode** phead,SlistDataType x);//頭插 void SlistPopFront(SlNode** phead);//頭刪SlNode* SlistFind(SlNode* head, SlistDataType x);//找元素SlNode* Slistinsert(SlNode* pos, SlistDataType x);//任意位置插入 SlNode* Slistdelete(SlNode* pos);//任意位置刪除void SlistPrint(SlNode* head);//打印函數文件
#include "Slist.h"void SlistPushBack(SlNode** phead, SlNode** ptail, SlistDataType x)//尾插-帶尾節點 {SlNode* NewNode = (SlNode*)malloc(sizeof(SlNode));NewNode->data = x;NewNode->next = NULL;if ((*phead) == NULL){(*phead)= NewNode;(*ptail) = NewNode;}else{(*ptail)->next = NewNode;(*ptail) = NewNode;}}//void SlistPushBack(SlNode** phead, SlistDataType x)//尾插 //{ // SlNode* NewNode = (SlNode*)malloc(sizeof(SlNode)); // if (NewNode == NULL) // { // printf("申請結點失敗\n"); // exit(-1); // } // NewNode->data = x; // NewNode->next = NULL; // SlNode* tail =(*phead); // if ((*phead)== NULL) // { // (*phead) = NewNode; // } // else // { // while (tail->next != NULL) // { // tail = tail->next; // } // tail->next=NewNode; // } // // // //}void SlistPopBack(SlNode** phead)//尾刪 {if ((*phead) == NULL){printf("錯誤,空鏈表");exit(-1);}else if ((*phead)->next == NULL){free(*phead);(*phead) = NULL;}else{SlNode* current = (*phead);SlNode* pre = NULL;while (current->next != NULL){pre = current;current = current->next;}free(current);pre->next = NULL;}} void SlistPushFront(SlNode** phead, SlistDataType x)//頭插 {SlNode* newNode = (SlNode*)malloc(sizeof(SlNode));newNode->data = x;newNode ->next= NULL;if (*phead == NULL){(*phead) = newNode;}else{newNode->next = (*phead);(*phead) = newNode;}} void SlistPopFront(SlNode** phead)//頭刪 {if ((*phead) == NULL){printf("錯誤,鏈表為空\n");exit(-1);}if((*phead)->next==NULL){(*phead) = NULL;}else{(*phead) = (*phead)->next;}} SlNode* SlistFind(SlNode* head, SlistDataType x) {SlNode* current = head;while (current->data != x){current = current->next;}return current;} SlNode* Slistinsert(SlNode* pos, SlistDataType x) {SlNode* newNode = (SlNode*)malloc(sizeof(SlNode));newNode->data = x;newNode->next = NULL;newNode->next = pos->next;pos->next = newNode;} SlNode* Slistdelete(SlNode* pos) {assert(pos);if (pos->next != NULL){SlNode* current = pos->next;pos->next = current->next;free(current);current = NULL;}} void SlistPrint(SlNode* head)//打印 {SlNode* current = head;//申請一個遍歷指針,因為頭指針不能亂跑while (current!= NULL){printf("%d->", current->data);current = current->next;//指針后移}printf("NULL"); }測試文件
#include "Slist.h"void test()//增刪測試 {SlNode* head=NULL;SlNode* tail = head;SlistPushFront(&head, 1);SlistPushFront(&head, 2);SlistPushFront(&head, 3);SlistPushFront(&head, 4);SlistPushFront(&head, 5);SlistPrint(head);printf("\n");//要刪元素4后面的3,首先拿到元素4的地址SlNode* find= SlistFind(head, 4);Slistdelete(find);SlistPrint(head);printf("\n"); }int main() {test();//增刪測試return 0; }總結
以上是生活随笔為你收集整理的数据结构-线性表之单链表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机看电视直播
- 下一篇: linux windows 动态库导出查