2.陣列、連結串列、跳錶的基本實現和特性 (7 天掌握演算法面試必考知識點)

zailushang發表於2020-07-18

全文內容主要源於極客大學的演算法課,僅作為筆記使用。

1、陣列

陣列:在記憶體中,佔用連續記憶體空間的,有序的元素序列。
陣列元素的型別沒有要求,即為泛型。

底層原理

當申請陣列時,記憶體管理器分配一個連續的記憶體地址。每一個地址可以直接通過記憶體管理器進行訪問。
如下圖所示,即為陣列相應的記憶體地址:

直接訪問的話,訪問第一個元素和訪問任意一個元素,時間複雜度都是一樣的,為O(1)。

陣列特性

訪問速度快

訪問陣列時,其實是利用指標,即記憶體地址,直接訪問對應記憶體地址中的數值,所以訪問速度非常快。
訪問陣列的時間複雜度:常數複雜度 O(1)

刪除和插入: O(n)

如下圖所示:
當向陣列中插入元素D時,首先要將E、F、G都向下挪動一個位置,然後將index=3的地址值賦值為D。
同理:

  • 最慢的插入操作:插入位置為第一個元素位置,要挪動n個元素,時間複雜度為O(n)
  • 最快的插入操作:插入位置為最後一個元素位置,不需要挪動元素,時間複雜度為O(1)
  • 平均要挪動一半元素

Array各操作時間複雜度

prepend: O(n),正常情況下是O(n),但是可以進行特殊優化到O(1)。初始化時申請稍大一些的記憶體空間,然後在陣列最開始預留一部分空間,然後prepend操作只需要把頭下標千一一個位置即可。
append: O(1)
lookup: O(1)
insert: O(n)
delete: O(n)

2、連結串列(LinkedList)

概念

連結串列:元素由Value和next組成,next指向下一個元素,在記憶體中是非連續空間。連結串列元素一般由class定義。
單向連結串列:如果只有一個next指標,是單向連結串列。
雙向連結串列:如果由兩個指標,next指向下一個元素,先前指標prev或previous指向上一個元素。一般頭叫做Head,尾叫做Tail。
迴圈連結串列:如果next指向Head,叫做迴圈連結串列。

LinkedList定義

最簡單的連結串列定義:

class LinkedList {
Node head; // head of list

/* Linked List Node */
class Node {
int data;
Node next;

Node(int d) { data = d; }
}

}

插入和刪除

增加節點


節點增加步驟:

  • 插入位置前面的node.next指向new node
  • new node的next指向插入位置後面的node 由於只有兩步操作,所以時間複雜度為O(1)

刪除節點

刪除節點為增加節點的逆操作。

刪除節點步驟:

  • target_node.prev_node.next = target_node.next_node 只有一步操作,時間複雜度為O(1)。

由此可見,連結串列的增加和刪除,沒有引起整個連結串列的群移操作,也不需要複製元素。所以移動或修改連結串列的效率非常高,時間複雜度為O(1)。
但是,正因為連結串列的這種特性,當訪問中間節點時,必須從Head Node一步一步往後找,時間複雜度為O(n)

連結串列各操作時間複雜度

3、跳錶 Skiped List

科學家在陣列和連結串列基礎上,優化了新的資料結構,即 跳錶。
主要關注:升維思想+空間換時間

跳錶的特點

注意:只能用於元素有序的情況。
所以,跳錶對標的是平衡樹(VAL Tree)和二分查詢,是一種插入/刪除/查詢 都是O(log n)的資料結構。
它最大的優勢是原理簡單、容易實現、方便擴充套件、效率更高。因此在一些熱門的專案裡用於替代平衡樹,如Redis、LevelDB等。

如何給有序的連結串列加速

有序一維資料結構加速理念:經常採用的方式就是升維,也就是說變成二維。
(1)加一級索引

第一級索引指向的是next.next
(2)加二級索引
二級索引next指向的是一級索引連結串列的next.next

(3)多級索引
以此類推,加多級索引如下圖所示:

跳錶查詢的時間複雜度

n/2、n/4、n/8,第k級索引節點的個數就是n/(2k)
假設索引有h級,最高階的索引有2個結點。n / (2h) = 2,從而求得 h = log2(n) - 1

假設原始連結串列要查詢1024次的話,那麼跳錶的時間複雜度是log2(n),即10次就可以找到元素。

現實中的跳錶

如下圖所示,現實中的跳錶,由於多次的增加和刪除,導致有些索引並不是完全工整的,最後經過多次改動後,有些地方的索引會多跨幾步,有些地方會少跨或只跨兩步。
維護成本相對較高,比如增加或刪除一個元素的話,索引要更新一遍,此時時間複雜度就變為Logn了。

空間複雜度

跳錶的空間複雜度為O(n),但是比原始連結串列要多很多。

4、更多

LRU快取演算法
跳錶在Redis中的使用
跳躍表、為啥 Redis 使用跳錶(Skip List)而不是使用 Red-Black?

相關文章