天天看點

圖解資料結構:線性表

1. 資料結構三要素

圖解資料結構:線性表

1)邏輯結構 指的是資料間的邏輯關系,與資料的存儲無關,獨立于計算機之外。它又分為線性結構和非線性結構

  • 線性結構:線性表,棧,隊列,串,數組和廣義表
  • 非線性結構:樹,圖,集合

2)存儲結構 是邏輯結構的存儲映像,就是資料間的關系在計算機中的表現形式。也成為實體結構。它又分為 4 類:順序存儲 ,鍊式存儲,索引存儲和散列存儲

  • 順序存儲:把邏輯上相鄰的元素存儲在實體位置也相鄰的存儲單元裡
  • 鍊式存儲:不要求實體位置的相鄰,借助訓示元素存儲位址的指針表示元素之間的邏輯關系
  • 索引存儲:在存儲元素資訊的同時,添加附加的索引表,通過索引對節點進行操作
  • 散列存儲:也稱 Hash 存儲,根據結點的關鍵字通過散列函數計算出結點的存儲位址

相同的邏輯結構在計算機裡可以用不同的存儲結構實作。比如邏輯結構中的線性結構,可以用數組(順序存儲)或單向連結清單(連結存儲)來實作。

3)資料運算: 施加在資料上的運算(包括定義與實作)。運算的定義是針對邏輯結構,運算的實作是針對實體結構

2. 線性表的定義

圖解資料結構:線性表

線性表定義:

  • 具有相同資料類型的 n 個資料元素的有限序列
  • 線性表是一種邏輯結構,表示元素之間一對一的邏輯關系

使用線性表存儲資料的方式可以這樣了解,把所有資料用一根線兒串起來,再存儲到實體空間中

下圖中,左側是“串”起來的資料,右側是空閑的實體空間。把這 “一串兒” 資料放置到實體空間,我們可以選擇以下兩種方式:

圖解資料結構:線性表

将具有“一對一”關系的資料“線性”地存儲到實體空間中,這種存儲結構就稱為線性存儲結構:

  • ① 順序表(如上圖左邊):将資料依次存儲在連續的整塊實體空間中
  • ② 連結清單(如上圖右邊):資料分散的存儲在實體空間中,通過一根線儲存着它們之間的邏輯關系
    • 單連結清單
    • 雙連結清單
    • 循環單連結清單
    • 循環雙連結清單

下面詳細講解這兩種存儲結構 ????

3. 順序表

① 順序表定義

線性表的順序存儲。邏輯上相鄰的兩個元素在實體位置上也相鄰

圖解資料結構:線性表

數組就是順序表,下标一般從 0 開始:

圖解資料結構:線性表

順序表的特點:

  • 随機通路(通過首位址和元素序号可在時間 O(1) 内找到元素)
  • 插入和删除需要移動大量元素
  • 存儲密度高,每個結點隻存儲資料元素

② 順序表基本操作

Ⅰ 插入

在數組 a 的第 i 個位置 (下标 i-1) 插入元素 e

// 第i個元素及其之後的元素後移for (int j = a.length; j >= i; j--)
    a[j] = a[j - 1];
a[i - 1] = e;
length++;複制代碼      
圖解資料結構:線性表
  • 最好情況:在表尾插入,時間複雜度 O(1)
  • 最壞情況:在表頭插入,時間複雜度 O(n)
  • 平均情況:時間複雜度 O(n)

Ⅱ 删除

删除數組 a 的第 i 個位置的元素

// 從第 i 個位置元素前移for(int j = i; j < a.length; j++) 
    a[j-1] = a[j];
length --;複制代碼      
圖解資料結構:線性表
  • 最好情況:删除表尾元素,時間複雜度 O(1)
  • 最壞情況:删除表頭元素,時間複雜度 O(n)

Ⅲ 按值查找

查找數組 a 中值為 e 的元素的下标

for (int i = 0; i < a.length; i++)if (a[i] == e)return i;複制代碼      
  • 最好情況:查找元素在表頭,時間複雜度 O(1)
  • 最壞情況:查找元素在表尾,時間複雜度 O(n)

4. 連結清單

① 連結清單的定義與結構

線性表的鍊式存儲。邏輯上相鄰的兩個元素在實體位置不一定也相鄰

例如,使用連結清單存儲 {1,2,3},資料的實體存儲狀态如下圖所示:

圖解資料結構:線性表

可以看到,上圖根本無法展現出各資料之間的邏輯關系。對此,連結清單的解決方案是,每個資料元素在存儲時都配備一個指針,用于指向自己的直接後繼元素。如下圖所示:

圖解資料結構:線性表

當然,指針可以指向自己的直接後繼元素,也可以指向自己的直接前驅元素。為此,連結清單可分為:

  • 單連結清單(指針指向自己的直接後繼元素)
  • 雙連結清單(指針指向自己的直接後繼元素和直接前驅元素)
  • 循環單連結清單(指針指向自己的直接後繼元素,表尾節點的指針指向頭節點)
  • 循環雙連結清單(指針指向自己的直接後繼元素和直接前驅元素,表尾節點的指針指向頭節點)

通過以上大家應該也知道了,連結清單中每個資料的存儲都由以下兩部分組成:

  • 資料域:資料元素本身
  • 指針域:指向該元素直接後繼/前驅/...元素的指針
圖解資料結構:線性表

上圖所示的結構在連結清單中稱為節點。也就是說,連結清單實際存儲的是一個一個的節點,真正的資料元素包含在這些節點中,舉個單連結清單的例子:

圖解資料結構:線性表

???? 當然,上所示的連結清單結構并不完整。一個完整的連結清單需要由以下幾部分構成:

  • 頭指針:一個普通的指針,它的特點是永遠指向連結清單第一個節點的位置。很明顯,頭指針用于指明連結清單的位置,便于後期找到連結清單并使用表中的資料
  • 節點:連結清單中的節點又分為頭節點、首元節點和其他節點
    • 頭節點:其實就是一個不存任何資料的空節點,通常作為連結清單的第一個節點。對于連結清單來說,頭節點不是必須的,它的作用隻是為了友善解決某些實際問題
    • 首元節點:由于頭節點(也就是空節點)的緣故,連結清單中稱第一個存有資料的節點為首元節點。首元節點隻是對連結清單中第一個存有資料節點的一個稱謂,用于和頭節點進行區分,沒有實際意義
    • 其他節點:連結清單中其他的節點

是以,一個存儲 {1,2,3} 的完整單連結清單結構如圖所示:

圖解資料結構:線性表

???? 引入頭節點的優點:

  • 連結清單的首元節點的操作與其他位置的元素操作一樣,無須進行特殊處理
  • 無論連結清單是否為空,其頭指針都是指向頭節點的非空指針,空表和非空表的處理得到了統一

② 單連結清單

Ⅰ 定義

單連結清單就是指針指向自己的直接後繼元素的連結清單

圖解資料結構:線性表

以 Java 為例,我們自定義一個單連結清單結構:

public class Node{private T t;private Node next;
    ......
}複制代碼      

單連結清單可以解決順序表需要大量連續存儲空間的缺點,但單連結清單附加指針域,也存在浪費存儲空間的缺點

單連結清單是非随機存儲的存儲結構:即不能直接找到表中某個特點的結點。需要從頭開始周遊。

單連結清單通路前驅的時間複雜度為 O(n),通路後繼 O(1)

Ⅱ 基本操作

頭插法

在頭節點 L 的後面插入節點 s,即 s 節點成為目前連結清單的首元節點

頭插法的讀入順序和生成順序相反

圖解資料結構:線性表
s.next = L.next;
L.next = s;複制代碼      
尾插法

在連結清單最後一個節點 r 的後面插入節點 s,即 s 節點成為目前連結清單的最後一個節點

尾插法的讀入順序和生成順序相同

圖解資料結構:線性表
r.next = s;
r = s; // s 節點成為連結清單的最後一個節點複制代碼      
插入節點

p 節點之後插入 s 節點

圖解資料結構:線性表
s.next = p.next;
p.next = s;複制代碼      
删除節點

p 節點之後删除 q 節點

圖解資料結構:線性表
// 删除 p 之後的 q q = p.next;
p.next = q.next;複制代碼      

③ 雙連結清單

雙連結清單就是同時具有前驅指針和後繼指針的連結清單

雙連結清單通路前驅和後繼結點時間複雜度都是 O(1)

圖解資料結構:線性表

以 Java 為例,我們自定義一個雙連結清單結構:

public class Node{private T t;private Node next;private Node prior;
    ......
}複制代碼      

圖解資料結構:線性表

上圖中 2、3 順序可調換

s.next = p.next;
p.next.prior = s;
p.next = s;
s.prior = p;複制代碼      

删除 p 節點之後的 s 節點

圖解資料結構:線性表
p.next = s.next;
s.next.prior = p;複制代碼      

③ 循環單連結清單

循環單連結清單就是表尾節點的指針指向頭節點的單連結清單

圖解資料結構:線性表

判空條件:表尾結點的 next 是否是等于頭指針

④ 循環雙連結清單

循環雙連結清單就是表尾節點的指針指向頭節點的雙連結清單

圖解資料結構:線性表

5. 順序表和連結清單的比較

1)存取方式

  • 順序表可以順序存取,也可以随機存取
  • 連結清單隻能從表頭順序存取元素

2)邏輯結構與實體結構

  • 順序存儲時,邏輯上相鄰的元素,對應的實體存儲位置也相鄰
  • 鍊式存儲時,邏輯上相鄰的元素,實體存儲位置不一定相鄰,其邏輯關系是通過指針連結來表示的

3)查找、插入和删除操作

  • 對于按值查找,順序表無序時,兩者的時間複雜度均為 O(n);順序表有序時,可采用折半查找,此時的時間複雜度為 O(log2n)

    對于按序号查找,順序表支援随機通路,時間複雜度僅為 O(1),而連結清單的平均時間複雜度為 O(n)。

  • 順序表的插入、删除操作,平均需要移動半個表長的元素

    連結清單的插入、删除操作,隻需要修改相關結點的指針域既可。由于連結清單的每個結點都有指針域,是以在存儲空間上要比順序表付出的代價大,存儲密度不夠大。

4)空間配置設定

  • 順序存儲在靜态存儲配置設定情況下,一旦存儲空間裝滿就不能擴充,若再加入新元素,則會出現溢出,是以需要預先配置設定足夠大的存儲空間。預先配置設定過大,可能會導緻順序表候補大量閑置;預先配置設定過小,又會造成溢出。

    動态存儲配置設定雖然存儲空間可以擴充,但需要移動大量元素,導緻操作效率降低,而且若記憶體中沒有更大的連續存儲空間,則會導緻配置設定失敗

繼續閱讀