七種基本數據結構

@jayce 數據結構本身是用於數據的存儲,其核心在於保存數據。
掌握一個數據解構,應該重點把握兩個方面:

  1. 該數據結構的特點有哪些?
  2. 該數據結構的增刪查是如何進行的?有哪些點值得注意?
  3. 該數據結構有哪些擴展形式,其特點和增刪查又是如何進行的?

本文創建原名: 第1章.[附加]如何纔算掌握了一個數據解構

總結

1. 鏈表

1.1 特點

  • 數據線性排列
  • 增刪很方便,查詢耗時
  • 每個數據項有一個指針指向下一個數據內存地址,在內存中,無需連續內存空間,可以分散存儲
  • 也正是因爲分散存儲,所以查詢時需要順着指針逐個遍歷
  • 時間複雜度: 查詢爲 O(n), 增加和刪除與n無關,所以爲 O(1)

1.2 增刪查

1.2.1 查

查詢,從首個元素開始,依據其指針指向的下一個元素,去遍歷。

1.2.2 增

只需要改變插入位置前後的指針指向即可:

"blue" --> "Yellow" --> "Red"
# 在第二個位置插入 “Green”
"blue" --> "Green"
"Green" --> "Yellow"
# ==>
"blue" --> "Green" --> "Yellow" --> "Red"

1.2.3 刪

將待刪除項的前一個元素指針指向待刪除項的後一個元素即可:

"blue" --> "Green" --> "Yellow" --> "Red"
# 刪除 Yellow, 直接將  "Green" 指向 "Red" 即可, "Yellow" 無需處理,如果需要再次用到其地址,直接覆蓋
"Green" --> "Red"
# ==>
"blue" --> "Green" --> "Red"

1.3 鏈表的擴展

處理基本鏈表, 還有兩種較爲常見的擴展鏈表,分別是 環形鏈表/循環鏈表 和 雙向鏈表

image-20230130110529790image-20230130110548815

循環鏈表,適合保存數量固定的最新數據

雙向鏈表的則可以前後雙向遍歷, (雙向鏈表存在兩個缺點:1. 指針數的增加會導致存儲空間需求增加;2.添加和刪除數據時需要改變更多指針指向。 )

2. 數組

2.1 特點

  • 數據呈線性排列,內存空間也是順序的
  • 數據的訪問十分簡單,數據的添加和刪除比較耗時
  • 時間複雜度:數據訪問爲 O(1), 增刪爲 O(n)
  • 數組相比較鏈表:數據訪問比鏈表快,但是數據的增刪都比鏈表慢

2.2 增刪查

2.2.1 查

數據的訪問,直接按照索引訪問

2.2.2 增

["a","b","c"]

要想在 "a"“b” 之間插入一個數據,

  1. 在數組末尾增加需要的存儲空間: ["a","b","c", - ]
  2. 然後自後往前向後移動,直到指定插入位置:["a","b", - ,"c"] --> ["a", - ,"b","c"]
  3. 最後在指定位置插入目標數據:["a","x","b","c"]

2.2.3 刪

  1. 首先刪除掉目標數據項:["a", - ,"b","c"]
  2. 從刪除位置開始,將後面的數據逐個像前移動:["a","b", - ,"c"] --> ["a","b","c", - ]
  3. 最後,刪除掉多餘的空間:["a","b","c"]

3. 棧(桶)

3.1 特點

  • 數據呈線性排列
  • 後進先出
  • 棧中的數據增刪只能在一端進行, 增加叫做 入棧操作, 刪除叫做 出棧操作

3.2 增刪查

3.2.1 查

棧很少討論如何查詢

3.2.2 增

在最後一個元素後追加元素

3.2.3 刪

刪除最後一個元素

4. 隊列

4.1 特點

  • 隊列的數據也是呈線性排列的
  • 隊列和數據有些相似,但是隊列是一個雙開口的管子, 數據的增加叫做入隊,數據的刪除叫做出隊
  • 先進先出

4.2 增刪查

4.2.1 查

隊列也基本不討論如何查詢

4.2.2 增

隊列的數據增加,從隊列的頭部操作 (入隊)

4.2.3 刪

隊列的數據刪除, 從隊列的尾部操作(出隊)

5. 哈希表

5.1 特點

  • 哈希表以數組 + 鏈表爲存儲容器

  • 數據的訪問和存儲都需要通過 哈希函數(Hash) 進行計算,算出 數組的鍵 ,然後進行 mod運算

    #運算規則:
    "Joe" --hash--> 4928 --> mod 5 --> 3
    # Joe 數據將被存儲到索引值爲 3 的數組位上。
    

    更多的

    #item	Hash	mod 5
    "Joe"	4828	3
    "Sue"	7291	1
    "Dan"	1539	4
    "Nell"	6276	1
    "Ally"	9143	3
    "Bob"	5278	3
    

    相同的鍵位 我們稱之爲 “衝突”, 這時候我們通過鏈表,將這些位置相同的數據項放在一起。

    [
    	,
    	"Sue" --> "Nell",
    	,
    	"Joe" --> "Ally" --> "Bob",
    	"Dan"
    ]
    

5.2 增刪查

5.2.1 查

計算 Hash 值,然後進行 Mod 運算,然後對數據對應的索引位上的鏈表進行線性查找。

例如要查詢 “Ally” , "Allay" ==> Hash函數 > 9143 mod 5 ==> 3, 然後對數組[3],進行鏈表的線性查找。找到 “Allay” 數據項。

5.2.2 增刪

哈希表結構的數據, 增刪都符合,先通過 Hash 函數計算出 Hash 值,然後進行 Mod 運算找到數組的索引位。 從而找到了數組中對應位的鏈表 。 接着,增刪操作均符合 鏈表的增刪操作特性。

關於哈希表的補充:

哈希表 中,我們可以利用 哈希函數 快速訪問到數組中的目標數據。 如果發生 哈希衝突 , 就使用 鏈表 進行存儲。如果使用數組的空間太小,使用哈希表的時候就容易發生衝突,線性查找的使用頻率也會更加高; 反過來,如果數組的空間太大, 就會出現很多空箱子,造成內存的浪費。 因此給數組設定合適的空間非常重要。

在存儲數據的過程中,如果發生衝突,通常有兩種方式去解決:

  1. 鏈地址法(同一位置的衝突對象組織在一起):可以利用鏈表在已有數據的後面插入新的數據來解決衝突。 這種方法被稱爲 “鏈地址法”。也就是上面 「特點」 中那樣

  2. 開放定址法(換個位置):一旦產生了衝突(該地址已經有其他的元素),就按某種規則去尋找另一空地址。

    • 若發生了第 \(i\) 次衝突,試探的下一個地址將會增加 \(d_i\), 基本公式是\((1\leq i \leq TableSize)\)

      \[h_i(key) = (h(key)+ d_i) \pmod {TableSize} \]

      \(d_i\) 決定了不同的解決衝突方案:

      • 線性探測:\(d_i = i\)
      • 平方探測:$ d_i = \pm i^2$
      • 雙散列:\(d_i = i\times h_2(key)\)

6. 堆

6.1 特點

  • 堆是一種圖的樹形結構,被用於實現 “優先隊列(priority queues)”

    優先隊列是一種數據結構,可以自由地添加數據,但是取出數據時要從最小值開始按照順序取出。

  • 堆的樹形結構中,各個頂點被稱之爲 “結點”, 數據就存儲於這些結點

  • 堆中的每個結點最多有兩個子結點

  • 結點的排列順序爲 從上到下, 同一行爲從左到右。

  • 子結點必定大於父結點

  • 新增結點 一般放於最下面一行靠左的位置,如沒有多餘空間,就往下另起一行,加在最左端

  • 時間複雜度:取出最小值的時間複雜度爲 \(O(1)\), 此外,取出數據後,堆需要重排,假設數據量爲n, 根據堆的形狀特點可知樹的高度爲 \(log_2n\), 時間複雜度即爲 \(O(logn)\).

6.2 增刪查

6.2.1 增

  1. 先將數據添加至最後一排靠左的位置,沒有空間則新增一排
  2. 判斷是否滿足子節點大於父節點,不滿足則和父節點互換位置,直到滿足大於父節點,否則,保持不動。

image-20230130135038930image-20230130135053198

image-20230130135106206image-20230130135122352

6.2.2 取

  1. 優先隊列取出數據的時候,取出的是最小值,也就是頂端結點。
  2. 取出後,其他位置需要頂替根結點的位置,把結點順序末尾的那個結點放置在根結點
  3. 然後和子節點比較,是否滿足子節點都大於根結點,不滿足則和較小的那個結點互換,依次往下,知道滿足基本條件(所有父結點小於任意根結點)

image-20230130135255749

頂端結點被取出之後,堆的結構需要被重排

image-20230130135336859

子結點必定大於父結點

image-20230130135441843

image-20230130135537615

7. 二叉樹

二叉樹即二叉查找樹(也叫做二叉搜索樹,二叉排序樹)。 這種數據結構採用了圖的樹形結構,只是數據組織規則有所不同。

7.1 特點

  • 每個結點最多有兩個子結點

  • 每個結點值大於其左子樹上任意一個結點值

  • 每個結點的值均小於其右子樹上的任意一個結點值

  • 所以最小值要從頂端開始,往其左下的末端尋找,最大值要從頂端開始,往其右下的末端尋找。

  • 時間複雜度: 最大值查找時間複雜度爲 O(1), 其他值,則取決於樹的形狀和高度, 如果結點樹爲 n, 且樹的形狀較爲均衡,比較大小和移動的次數最多就是 \(log_2n\) , 因此時間複雜度也就是 \(O(log_n)\), 但是如果樹的形狀朝單側縱向延伸,樹就會變得很高,此時時間複雜度也就變成了\(O(n)\)

    image-20230130140831906image-20230130140842346

7.2 增刪查

7.2.1 增

二叉樹的增加,從頂部開始

image-20230130141041053

接着, 1 < 9, 接着往左移到 9 所在結點,然後,1 < 3 大,所以接着往左移,填補空位:

image-20230130141341716

同理,如果插入 4

image-20230130141406451

4 < 15 ==>左移
4 < 9 ==>左移
4 > 3 ==>右移
4 < 8, 其8 無其他子結點,則作爲其子結點

image-20230130141606059

7.2.2 刪

如果需要刪除的結點沒有子結點,那麼直接刪掉該結點即可:

image-20230130141714066

如果需要刪除的結點只有一個子結點,那麼刪除掉目標結點後,然後把子結點移到被刪除結點的位置即可。

image-20230130141825775image-20230130141836707

如果被刪除結點有兩個結點,那麼先刪除該結點,然後在該結點左子樹中尋找最大結點,移動到被刪除結點位置。

image-20230130142000128image-20230130142010474

7.2.1 查找

二叉樹的查找,類似於二叉樹的結點增加,也是從頂部開始,將根結點和目標結點比較,如果目標結點小於根結點,則向左移,否則右移動,依照此規則往下遍歷,直到找到目標元素。

7.3 二叉樹的擴展

有很多以二叉樹爲基礎擴展的數據結構,比如 “平衡而茶查找樹”, 這種數據結構可以修正形狀不均衡的樹,讓其始終保持均衡狀態,以提高查找效率。

此外,結點樹並不是必須爲兩個結點,可以擴展爲 m 個結點。像這種子結點樹可以自由設定,並且形狀均衡的樹,我們稱作 B 樹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章