【學習總結】《大話數據結構》- 總
第3章線性表-代碼鏈接
啟示:
-
線性表:零個或多個數據元素的有限序列。
目錄
- 3.1 開場白
- 3.2 線性表的定義
- 3.3 線性表的抽象數據類型
- 3.4 線性表的順序存儲結構
- 3.5 順序存儲結構的插入與刪除
- 3.6 線性表的鏈式存儲結構
- 3.7 單鏈表的讀取
- 3.8 單鏈表的插入與刪除
- 3.9 單鏈表的整表創建
- 3.10 單鏈表的整表刪除
- 3.11 單鏈表結構與順序存儲結構優缺點
- 3.12 靜態鏈表
- 3.13 循環鏈表
- 3.14 雙向鏈表
- 3.15 總結回顧
- 3.16 結尾語
========================================
3.1 開場白
- 一些可以略過的場面話...
- 由一個例子引入:幼兒園小朋友排隊,左右兩側每次都是固定的人,方便清點和避免丟失。
========================================
3.2 線性表的定義
-
定義:線性表(list)- 零個或多個數據元素的有限序列。
-
幾個關鍵點:
1-“序列”:第一個元素無前驅,最后一個元素無后繼,其他每個元素都有且只有一個前驅和后繼。
2-“有限”:元素的個數是有限的。
3-再加一點:元素類型相同。 -
-
數學語言定義:
-
線性表的長度:線性表元素的個數n(n≥0)定義為線性表的長度。n=0時,稱為空表。
-
位序:ai是第i個數據元素,稱i為數據元素ai在線性表中的位序。
-
-
一些線性表的例子:
- 12星座-有序,有限,是線性表
- 公司的組織架構,一個boss手下幾個人那種:不是線性表,每個元素不只有一個后繼。
- 班級同學之間的友誼:不是線性關系,每個人都可以和多個同學建立友誼
- 愛情:不是線性關系,否則每個人都有一個愛的人和被愛的人且不是同一個人....
- 班級點名冊:是線性表,按學號排序,有序有限,類型相同,並且作為復雜的線性表,一個數據元素由若干個數據項組成。
- 書包占位所以要插隊:不能,因為排隊是線性表,而書包數據類型不同,別人都是人,書包不是人,所以不是線性表,不能插隊。
========================================
3.3 線性表的抽象數據類型
-
第一章講過“抽象數據類型”,大致包含類型名稱、data、operation等
-
線性表的操作<對比幼兒園小朋友排隊>:
-
線性表的創建和初始化過程:老師給小朋友們排一個可以長期使用的隊
-
線性表重置為空表的操作:排好后發現高矮不一,於是解散重排
-
根據位序得到數據元素:問到隊伍里第5個小朋友是誰,老師很快能說出來這個小朋友的名字、家長等。
-
查找某個元素:找一下麥兜是否是班里的小朋友,老師會告訴你,不是的
-
插入數據和刪除數據:有新來的小朋友,或者有小朋友生病請假時
-
-
線性表的抽象數據類型定義:
-
涉及更復雜的操作時,可以用以上的基本操作的組合來實現。
-
例如求兩個線性表集合A和B的並集:可以把存在B中但不存在A中的數據元素插入到A中
-
========================================
3.4 線性表的順序存儲結構
-
這部分了解一下線性表的兩種物理結構之一----順序存儲結構
-
1-定義:線性表的順序存儲結構,指用一段地址連續的存儲單元依次存儲線性表的數據元素。
-
2-順序存儲方式:
-
依次,每個數據元素類型相同,可以用c語言的一維數組來實現順序存儲結構。
-
代碼實現:
-
描述順序存儲結構需要三個屬性:
- 1-存儲空間的起始位置:數組data,它的存儲位置就是存儲空間的存儲位置
- 2-線性表的最大存儲容量:數組長度MaxSize
- 3-線性表的當前長度:length
-
-
3-數據長度和線性表長度的區別
-
數組長度:是存放線性表的存儲空間的長度,存儲分配后這個量是一般不變的。
-
線性表長度:線性表中數據元素的個數,隨着插入刪除操作而變化。
-
注意:線性表的長度總是小於等於數組的長度。
-
-
4-地址的計算方法
-
地址:存儲器中的每個存儲單元都有自己的編號,這個編號稱為地址。(內存地址)
-
計數:線性表從1開始,而c語言中的數組從0開始
-
LOC:獲得存儲位置的函數(假設每個數據元素占c個存儲單元)
-
-
通過上述公式,可以隨時算出線性表中任意位置的地址,且都是相同時間。
-
即時間復雜度:O(1) -- 每個位置存、取數據,都是相等的時間,也就是一個常數。
-
通常把具有這一特點的存儲結構稱為<隨機存取結構>
========================================
3.5 順序存儲結構的插入與刪除
-
0-注意點
-
0.1-線性表的第 i 個數是從1開始計數,而數組下標[]是從0開始計數
-
0.2-時刻保持清醒,分清到底是 i 還是 i-1
-
0.3-L->data和L.data:(點出現在取數中,箭頭出現在插入刪除中)
-
L->data中L是結構體指針;L.data中L是結構體變量;一說:點適合順序結構,箭頭適合鏈式結構。
-
-
-
1-獲得元素操作 - GetElem
-
2-插入操作
-
圖示:
-
-
插入算法的思路:
- 1-如果插入位置不合理,拋出異常;
- 2-如果線性表長度大於等於數組長度,則拋出異常或動態增加容量;
- 3-從最后一個元素開始向前遍歷到第i個位置,分別將它們都向后移動一個位置;
(注:從最后一個元素開始,依次往后挪一位,否則最后一個不動,前面沒位置的) - 4-將要插入元素填入位置i處;
- 5-表長加1.
-
代碼實現:
(注:if中排除i>length+1是符合的,因為跟表不挨着,插入和表無關啦,而if中不包含臨界點i=length+1的情況,因為if進行的是判斷錯誤和移動數據,當i=length+1時,直接運行插入數據賦值即可,即if判斷后的那條語句)
-
3-刪除操作
-
圖示:
-
-
刪除算法的思路:
- 1-如果刪除位置不合理,拋出異常;
- 2-取出刪除元素;
- 3-從刪除元素位置開始遍歷到最后一個元素位置,分別將它們都向前移動一個位置;
(注:從刪除元素的位置開始,依次往前挪,后面的跟上,就把空位補上了) - 4-表長減1.
-
代碼實現:
-
4-插入、刪除的時間復雜度分析:O(n)
-
最好的情況:在最后一個位置插入或刪除:O(1)
-
最壞的情況:在第一個位置插入或刪除:O(n)
-
平均:(n-1)/2
-
-
5-線性表的順序存儲結構---不同操作的時間復雜度對比:
-
存取操作:O(1)
-
插入刪除:O(n)
-
綜上:順序存儲結構適合元素個數不太變化的,更多是存取數據的應用
-
-
6-線性表的順序存儲結構的優缺點:
========================================
3.6 線性表的鏈式存儲結構
-
1-順序存儲結構的不足的解決方法
-
順序存儲的最大缺點:插入和刪除需要移動大量元素,非常耗費時間。
-
原因:相鄰元素的存儲位置也具有鄰居關系,它們在內存中的位置是挨着的,無法快速介入。
-
思路:不要考慮元素的相鄰位置,只讓每個元素知道它的下一個元素的地址,可以找到即可
-
-
2-線性表鏈式存儲結構的定義
-
特點:用一組任意的存儲單元存儲線性表的數據元素,這組存儲單元可以連續,也可以不連續。(順序存儲要求地址連續)
-
負擔:順序結構中,每個數據元素只需要存數據元素的信息,而鏈式結構中,還需要存儲它的后繼元素的存儲地址。
-
頭指針:鏈表中第一個結點的存儲位置叫做頭指針。整個鏈表的存取從頭指針開始。
最后一個結點的指針:后繼不存在,應為空,通常用NULL或“^”表示是
-
頭結點:為方便對鏈表進行操作,在單鏈表的第一個結點前附設一個結點,稱為頭結點。
頭結點的數據域可以不存儲任何信息,也可以存儲如線性表的長度等附加信息,頭結點的指針域存儲第一個結點的指針。
-
-
3-頭結點與頭指針的異同
-
注:頭指針-->(頭結點)-->開始結點(第一個結點)
- 一個單鏈表可以由其頭指針唯一確定,一般用其頭指針來命名單鏈表
- 不論鏈表是否為空,頭指針總是非空
- 單鏈表的頭指針指向頭結點。
- 頭結點的指針域存儲指向第一結點的指針(即第一個元素結點的存儲位置)
- 若線性表為空表,則頭結點的指針域為空。
-
參考:數據結構中的開始結點、頭指針、頭結點
-
4-線性表鏈式存儲結構的代碼描述
-
空鏈表圖示:線性表為空表,則頭結點的指針域為‘空’
-
-
更方便的單鏈表圖示:
-
帶有頭結點的單鏈表圖示:
-
新的空鏈表圖示:
-
單鏈表的代碼:(用結構指針來描述)
-
結點:由存放數據元素的數據域,和存放后繼結點地址的指針域組成。
- 設p是指向線性表第i個元素的指針,則
- p->data:其值是一個數據元素,表示結點ai的數據域
- p->next:其值是一個指針,表示結點ai的指針域,指向第i+1個元素
- p->data=ai
- p->next->data=ai+1
- p和(p->next)都是指針,同等看待
========================================
3.7 單鏈表的讀取
-
相比順序存儲結構的非常容易的讀取,要得到單鏈表的第i個元素,必須從頭開始找
-
算法思路:
- 1-聲明一個結點p指向鏈表第一個結點,初始化j從1開始;
- 2-當j<i時,就遍歷鏈表,讓p的指針向后移動,不斷指向下一結點,j累加1;
- 3-若到鏈表末尾p為空,則說明第i個元素不存在;
- 4-否則查找成功,返回結點p的數據。
-
代碼實現:
(ps: 鏈表L的頭結點是L->next??)
-
時間復雜度:O(n)
-
最好的情況:i=1時不需要遍歷
-
最壞的情況:i=n時遍歷n-1次
-
PS:事先不知道要循環的次數,因此不方便用for循環。while循環也是循環!!!~~
-
-
核心思想:“工作指針后移”
========================================
3.8 單鏈表的插入與刪除
-
1-單鏈表的插入
-
將s結點插入到結點p和p->next之間
-
-
操作代碼:
s->next=p->next; p->next=s;
-- 把p的后繼結點改為s的后繼結點,再把結點s變成p的后繼結點<順序不可換否則會覆蓋p->next的值>
-
單鏈表的表頭和表尾的操作:
-
單鏈表第i個數據插入結點的算法思路:
- 1-聲明一結點p指向鏈表第一個結點,初始化j從1開始;
- 2-當j<i時,就遍歷鏈表,讓p的指針向后移動,不斷指向下一結點,j累加1;
- 3-若到鏈表末尾p為空,則說明第i個元素不存在;
- 4-否則查找成功,在系統中生成一個空節點s;
- 5-將數據元素e賦值給s->data;
- 6-單鏈表的插入標准語句:
s->next=p->next; p->next=s;
- 7-返回成功。
-
代碼實現:
-
2-單鏈表的刪除
-
將結點q刪除,其中q為存儲元素ai的結點。
-
其實就是將它的前繼結點的指針繞過,指向它的后繼結點即可。
-
-
操作代碼:
q=p->next; p->next=q->next;
(p->next=p->next->next,用q來取代p->next)
-
單鏈表第i個數據刪除結點的算法思路:
- 1-聲明一結點p指向鏈表第一個結點,初始化j從1開始;
- 2-當j<i時,就遍歷鏈表,讓p的指針向后移動,不斷指向下一個結點,j累加1;
- 3-若到鏈表末尾p為空,則說明第i個元素不存在;
- 4-否則查找成功,將欲刪除的結點p->next賦值給q;
- 5-單鏈表的刪除標准語句:
p->next=q->next
; - 6-將q結點中的數據賦值給e,作為返回;
- 7-釋放q結點;
- 8-返回成功。
-
代碼實現:
-
3-時間復雜度分析
-
單鏈表的插入和刪除:首先是遍歷查找第i個元素,其次是插入和刪除操作
-
故時間復雜度是O(n)
-
當插入刪除一個元素時,與順序結構比,沒有太大優勢
-
但是當從第i個元素的位置插入10個元素時:
-
順序結構:每次插入都需要移動元素,每次都是O(n)
-
單鏈表:只需要在第一次時,找到第i個位置的指針,此時為O(n),后面每次都是O(1)
-
-
綜上,對於插入或刪除數據越頻繁的操作,單鏈表的效率優勢就越是明顯。
-
========================================
3.9 單鏈表的整表創建
-
對比順序結構和單鏈表的創建:
-
順序存儲結構的創建:相當於一個數組的初始化,即聲明一個類型和大小的數組並賦值的過程。
-
單鏈表的創建:動態結構,對於每個鏈表,它所占空間的大小和位置不需要預先分配划定,可根據需求即時生成。
-
故:創建單鏈表:一個動態生成鏈表的過程,即從“空表”的初始狀態起,依次建立各元素結點,並逐個插入鏈表。
-
-
單鏈表整表創建的算法思路:
- 1-聲明一結點p和計數器變量i;
- 2-初始化一空鏈表L;
- 3-讓L的頭結點的指針指向NULL,即建立一個帶頭結點的單鏈表;
- 4-循環:
- 生成一新結點賦值給p;
- 隨機生成一數字賦值給p的數據域p->data;
- 將p插入到頭結點與前一新結點之間。
-
頭插法:
-
頭插法思路:始終讓新結點在第一的位置
-
頭插法--代碼實現:
-
注:先把 ( * L ) -> next 賦值給 p 結點的指針域,再把 p 結點賦值給頭結點的指針域
否則會覆蓋 ( * L ) -> next
-
頭插法--圖示:
-
尾插法:
-
尾插法思路:新結點插在終端結點的后面
-
尾插法--代碼實現:
(注:頭插法先定義一個頭結點,而尾插法不需要頭結點)
-
尾插法--圖示:
-
注:L是指整個單鏈表,r是指向尾結點的變量,r隨着循環不斷地變化結點,而L隨着循環增長為一個多結點的鏈表
循環結束后,
r->next=NULL
--讓這個鏈表的指針域置空。 -
========================================
3.10 單鏈表的整表刪除
-
單鏈表整表刪除的算法思路:
- 1-聲明一結點p和q;
- 2-將第一個結點賦值給p;
- 3-循環;
- 將下一結點賦值給q;
- 釋放p;
- 將q賦值給p
-
代碼實現:
========================================
3.11 單鏈表結構與順序存儲結構優缺點
-
簡單對比單鏈表結構和順序存儲結構;
-
對比得出的一些經驗性的結論:
-
1-若線性表多查找,少插刪,宜采用順序存儲結構;若多查刪,宜采用單鏈表結構
舉例:游戲開發中,用戶注冊信息時,除了注冊時插入,其他時候多讀取,用順序;而玩家的裝備隨時增刪,用單鏈表。
-
2-若線性表的元素個數變化大或不確定時,宜采用單鏈表;若確定或事先明確個數,用順序存儲效率更高。
-
3-總結:各有優缺點,不能簡單說哪個好哪個不好,需要根據實際情況綜合分析哪種更合適。
-
========================================
3.12 靜態鏈表
-
引入:
- 1-出現問題:c有指針,而有些面向對象語言如java、c#等啟用了對象引用機制,間接實現了指針的作用;而有些語言如basic等沒有指針,如何實現鏈表結構呢?
- 2-解決思路:用數組代替指針,來描述單鏈表
-
定義:用數組描述的鏈表叫做靜態鏈表,或曰“游標實現法”。
-
數組的元素都是由兩個數據域組成的,data(數據)和cur(游標)
-
即數組的每個下標都對應一個data和一個cur
- 數據域data:用來存放數據元素,也就是我們要處理的數據。
- 游標cur:相當於單鏈表中的next指針,存放該元素的后繼在數組中的下標。
- 注:此處的游標cur不是數組的索引,而是數組索引之外的又一個值
-
備用鏈表:通常把未被使用的數組元素稱為備用鏈表。(即數組的后面還沒填充數據的空閑空間)
-
對於數組特殊元素的設置:
-
上圖示此時相當於初始化的數組狀態。對應的代碼實現:
-
將數據存入后的狀態:
(注,左上角筆誤:空閑空間,不是空間空間。。還有圖中標記的所謂“頭結點”存疑,可以說是開始結點) -
-
1-靜態鏈表的插入操作
-
動態鏈表中:結點的申請和釋放分別借用函數malloc()和free()來實現
-
靜態鏈表中:操作的是數組,需要自己實現這兩個函數,以便進行插刪操作
-
malloc()函數的靜態鏈表代碼實現:(配合上圖食用效果更佳)
-
靜態鏈表插入操作的代碼實現
-
靜態鏈表插入操作的圖示:(在乙和丁之間插入丙,只需改變乙和丙的cur即可)
-
2-靜態鏈表的刪除操作
-
free()函數的靜態鏈表代碼實現:
-
靜態鏈表刪除操作的代碼實現:
-
靜態鏈表刪除操作的圖示:()
-
3-靜態鏈表優缺點
========================================
3.13 循環鏈表
-
循環鏈表(circular linked list)的定義:
-
將單鏈表中終端結點的指針端由空指針改為指向頭結點,就使整個單鏈表形成了一個環
-
這種頭尾相接的單鏈表稱為單循環鏈表,簡稱循環鏈表
-
-
優勢:
解決了一個問題:即如何從當中一個結點出發,訪問到鏈表的全部結點。
-
帶有頭結點的循環鏈表圖示-空鏈表和非空鏈表
-
循環鏈表和單鏈表的差異:循環的判斷條件
-
單鏈表:判斷p->next是否為空
-
循環鏈表:判斷p->next不等於頭結點,則循環未結束
-
-
尾指針rear
-
引入: 有頭結點時,訪問第一個結點用 O(1),但訪問最后一個結點用O(n)
-
有了尾指針以后,訪問第一個結點和最后一個結點的時間均為O(1)了
- 尾指針:rear
- 頭指針:rear->next
-
尾指針圖示:
-
-
示例:將兩個循環鏈表合並成一個表時:
-
圖示:
-
-
代碼實現:
========================================
3.14 雙向鏈表
-
雙向鏈表(double linked list)的定義:
在單鏈表的每個結點中,再設置一個指向其前驅結點的指針域
即在雙向鏈表中的結點都有兩個指針域,一個指向直接后繼,一個指向直接前驅
-
代碼實現:
-
帶有頭結點的雙向鏈表的圖示:空鏈表和非空鏈表
-
雙向鏈表的前驅的后繼:它自己
-
p->next>prior = p = p->prior->next
-
-
雙向鏈表的插入操作:
注意順序:先s的前驅和后繼,再后結點的前驅,最后前結點的后繼
-
雙向鏈表的刪除操作:
-
好處和弊端分析
-
弊端:在插入刪除時,需要更改兩個指針變量,並且占用更多空間
-
好處:良好的對稱性,有效提高算法的時間性能
-
綜上:用空間換時間
-
========================================
3.15 總結回顧
========================================