紅黑樹、散列表、跳錶理解入門

跳錶

跳錶是什麼?

就是把鏈表的結構稍加改造,這種數據結構叫 \color{red}{跳錶}

爲什麼要改造鏈表呢?

爲了提升鏈表的查詢效率,怎麼讓鏈表支持類似‘數組’那樣的‘二分’算法呢

簡單理解跳錶

跳錶是一個各方面性能都比較優秀的 動態數據結構,可以支持快速地插入、刪除、查找操作,寫起來也不復雜,甚至可以替代紅黑樹。

Redis 中的有序集合(Sorted Set)就是用跳錶來實現的。
那 Redis 爲什麼會選擇用跳錶(和散列表)來實現有序集合呢? 爲什麼不用紅黑樹呢?這個問題一會在回答,先看看跳錶的數據結構

跳錶數據結構

其實概念很簡單,就是在鏈表上加上了 \color{red}{索引層}

 
跳錶數據結構

是不是很像二分法呢,如果每兩個結點會抽出一個結點作爲上一級索引的結點,最後留下2個結點,那時間複雜度就是 O(logn)
這個查找的時間複雜度跟二分查找是一樣的,不過對於單鏈表是用空間換來的。也可能通過每3個結點抽出一個做爲索引,也可以極大的節省內存。

 

爲什麼叫動態數據結構

當我們在不停插入數據,如果我們不更新索引,可能出現某 2 個索引結點之間數據非常多的情況。極端情況下,跳錶還會退化成單鏈表。
紅黑樹、AVL 樹這樣平衡二叉樹,是通過左右旋的方式保持左右子樹的大小平衡,而跳錶是通過隨機函數來維護平衡性。

插入、刪除、查找以及迭代輸出有序序列這幾個操作,紅黑樹也可以完成,時間複雜度跟跳錶是一樣的。但是,按照區間來查找數據這個操作,紅黑樹的效率沒有跳錶高。

對於按照區間查找數據這個操作,跳錶可以做到 O(logn) 的時間複雜度定位區間的起點,然後在原始鏈表中順序往後遍歷就可以了。

PS: B+樹就把葉子節點連起來

Redis 鍵值構建一個散列表,這樣按照 key 來刪除、查找一個成員對象的時間複雜度就變成了 O(1)。同時,藉助跳錶結構,其他操作也非常高效。


散列表

散列表的英文叫“Hash Table”,我們平時也叫它“哈希表”或者“Hash 表”

\color{red}{散列表用的是數組支持按照下標隨機訪問數據的特性,}
\color{red}{所以散列表其實就是數組的一種擴展,由數組演化而來。}
\color{red}{可以說,如果沒有數組,就沒有散列表}

散列技術是在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關係 f,使得每個關鍵字 key 對應一個存儲位置 f(key)。查找時根據這個對應關係匠互給定的 key 的映射 f(key)

存儲位置 = f(關鍵字)

這種關係 f 稱爲散列函數(又稱哈希函數)。散列技術將記錄存儲在一塊連續的存儲空間中,這塊連續存儲空間稱爲散列表或哈希表。那麼關鍵字對應的記錄存儲位置稱爲散列地址。

散列函數的構造方法

散列函數的構造方法特點就是:計算簡單、散列地址分佈均勻

  • 直接定址法
  • 數學分析法
  • 平方取中法
  • 摺疊法
  • 除留餘數法
  • 隨機數法

大家一定聽說過 hash 碰撞。就是2個不同的 key 對應着不同的 f 關係。但這是幾乎不可能的,即便像業界著名的MD5、SHA、CRC等哈希算法,也無法完全避免這種散列衝突。而且,因爲數組的存儲空間有限,也會加大散列衝突的概率。

散列衝突

我們只能通過其它途徑來尋找方法。我們常用的散列衝突解決方法有兩類,開放尋址法(open addressing)和鏈表法(chaining)。

開放尋址法(open addressing)

所謂的開放尋址法就是一但發生了衝突,就去尋找下一個空的散地址,只要散列表足夠大,空的散列表地址總能找到,並將記錄存入。

 
image.png
 
公式

鏈表法(chaining)

鏈地址法又稱鏈表法,其實當發生衝突時存入鏈表,如下圖很容易就可以看明白。此時,已經不存在什麼衝突地址的問題,無論有多少衝突,都只是在當前位置給單鏈表增加結點的問題。

 
image.png

公共益出區法

這種不常見,就是把衝突的單獨找個地方。


紅黑樹

顧名思義,紅黑樹中的節點,一類被標記爲黑色,一類被標記爲紅色。除此之外,一棵紅黑

平衡二叉樹 是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度不能大於 1

紅黑樹是一種平衡二叉查找樹。它是爲了解決普通二叉查找樹在數據更新的過程中,複雜度退化的問題而產生的。紅黑樹的高度近似 log2n,所以它是近似平衡,插入、刪除、查找操作的時間複雜度都是 O(logn)。

平衡二叉查找樹其實有很多,比如,Splay Tree(伸展樹)、Treap(樹堆)等,但是我們提到平衡二叉查找樹,聽到的基本都是紅黑樹。
紅黑樹在衆多裏面,表現的最爲平衡。
“近似平衡”就等價爲性能不會退化得太嚴重。

一棵紅黑樹還需要滿足這樣幾個要求:

  • 根節點是黑色的;
  • 每個葉子節點都是黑色的空節點(NIL),也就是說,葉子節點不存儲數據;
  • 任何相鄰的節點都不能同時爲紅色,也就是說,紅色節點是被黑色節點隔開的;
  • 每個節點,從該節點到達其可達葉子節點的所有路徑,都包含相同數目的黑色節點;

看到這裏你會很頭大,什麼黑的紅的,完全不懂。賦上連接,有時間在看

總結

散列表:插入刪除查找都是O(1), 是最常用的,但其缺點是不能順序遍歷(存入的數據是無順序的)以及擴容縮容的性能損耗。適用於那些不需要順序遍歷,數據更新不那麼頻繁的。
散列表總和鏈表、跳錶一起出現組合使用。

跳錶:插入刪除查找都是O(logn), 並且能順序遍歷。缺點是空間複雜度O(n)。適用於不那麼在意內存空間的,其順序遍歷和區間查找非常方便。
跳錶還可以和散列表組合讓刪除、查找一個成員對象操作變爲O(1),也就是說利用了散列表查找速度,跳錶的順序結構

紅黑樹:插入刪除查找都是O(logn), 中序遍歷即是順序遍歷,穩定。缺點是難以實現,去查找不方便。其實跳錶更佳,但紅黑樹已經用於很多地方了。

 

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