【學習總結】《大話數據結構》- 第3章-線性表


【學習總結】《大話數據結構》- 總

第3章線性表-代碼鏈接

啟示:

  • 線性表:零個或多個數據元素的有限序列。

目錄

========================================

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 總結回顧

========================================

3.16 結尾語

END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM