映射表map(平衡二叉树实现)_手动实现Java集合容器之TreeMap(上)
上一篇我們手寫(xiě)了HashMap,還有一個(gè)很重要的Map的實(shí)現(xiàn)類(lèi)TreeMap。打開(kāi)源碼第一句話:* A Red-Black tree based {@link NavigableMap} implementation.TreeMap是一個(gè)基于紅黑樹(shù)的實(shí)現(xiàn)。對(duì)紅黑樹(shù)沒(méi)有了解怎么辦,那就先搞清楚紅黑樹(shù)的原理。只要理解紅黑樹(shù)的玩法,TreeMap實(shí)現(xiàn)起來(lái)就沒(méi)有那么大的難度了。
紅黑樹(shù)(Red Black Tree) 是一種自平衡二叉查找樹(shù)。所以我們先搞明白什么是二叉查找樹(shù)和完美平衡二叉樹(shù)。
二叉排序樹(shù)(Binary Sort Tree)是具有下列性質(zhì)的二叉樹(shù):
- 查找:從根結(jié)點(diǎn)開(kāi)始查找,根據(jù)每一次比較結(jié)果,在當(dāng)前結(jié)點(diǎn)的左子樹(shù)與右子樹(shù)選擇其一,從而縮小一半的查找范圍。如果到達(dá)葉子結(jié)點(diǎn)仍然不相等,則查找不成功。
- 插入:首先使用查找算法確定元素的插入位置。如果查找成功,說(shuō)明相同元素已經(jīng)存在,則不插入;否則在查找不成功的一條路徑之尾插入結(jié)點(diǎn),作為葉子結(jié)點(diǎn)。
- 刪除:a. 葉結(jié)點(diǎn)直接刪除 b. 如果被刪除的結(jié)點(diǎn)有一個(gè)子結(jié)點(diǎn),將子結(jié)點(diǎn)移到被刪除的元素的位置。c. 采用中序遍歷(左-根-右),找到待刪除的結(jié)點(diǎn)的后繼結(jié)點(diǎn),將其與待刪除的結(jié)點(diǎn)互換,然后刪除待刪除的結(jié)點(diǎn)。假如我們要?jiǎng)h除29,中序遍歷結(jié)果為10-11-13-20-27-29-31-32-39-41-53-50-65-72-91,所以29的后繼結(jié)點(diǎn)是31,然后互換位置,刪除29。
- 二叉查找樹(shù)的查詢復(fù)雜度,和二分查找一樣,插入和查找的時(shí)間復(fù)雜度均為 O(logn) ,但是在最壞的情況下仍然會(huì)有 O(n) 的時(shí)間復(fù)雜度。原因在于插入和刪除元素的時(shí)候,樹(shù)沒(méi)有保持平衡。
為了降低二叉排序樹(shù)的高度,提高查找效率。平衡二叉樹(shù)(又稱(chēng)為AVL樹(shù))。平衡二叉樹(shù)(Balanced Binary Tree)是具有下列性質(zhì)的二叉排序樹(shù):
插入:如果插入一個(gè)結(jié)點(diǎn)后破壞了二叉樹(shù)的平衡性,需要調(diào)整一棵最小不平衡子樹(shù)。最小不平衡子樹(shù)是離插入結(jié)點(diǎn)最近,且以平衡因子絕對(duì)值大于1的結(jié)點(diǎn)為根的子樹(shù)。若出現(xiàn)不平衡,則要根據(jù)新插入的結(jié)點(diǎn)與最低不平衡結(jié)點(diǎn)的位置關(guān)系進(jìn)行相應(yīng)的調(diào)整。分為L(zhǎng)L,RR,LR,RL四種類(lèi)型。以下只介紹了最簡(jiǎn)單的情況。
LL型:在最低不平衡結(jié)點(diǎn)的左孩子的左子樹(shù)上插入結(jié)點(diǎn)。插入C后,A的平衡因子由1增加到2 。結(jié)點(diǎn)B變成新的根結(jié)點(diǎn)。A變成右孩子結(jié)點(diǎn),C變成左孩子結(jié)點(diǎn)。
RR型:在最低不平衡結(jié)點(diǎn)的右孩子的右子樹(shù)上插入結(jié)點(diǎn)。結(jié)點(diǎn)B變成新的根結(jié)點(diǎn)。C變成右孩子結(jié)點(diǎn),A變成左孩子結(jié)點(diǎn)。
LR型:在最低不平衡結(jié)點(diǎn)的左孩子的右子樹(shù)上插入結(jié)點(diǎn)。結(jié)點(diǎn)C變成新的根結(jié)點(diǎn)。A變成右孩子結(jié)點(diǎn),B變成左孩子結(jié)點(diǎn)。
RL型:在最低不平衡結(jié)點(diǎn)的右孩子的左子樹(shù)上插入結(jié)點(diǎn)。結(jié)點(diǎn)C變成新的根結(jié)點(diǎn)。B變成右孩子結(jié)點(diǎn),A變成左孩子結(jié)點(diǎn)。
平衡二叉樹(shù)似乎完美解決了二叉排序樹(shù)的問(wèn)題,通過(guò)旋轉(zhuǎn)使樹(shù)的高度最小化,對(duì)于有 n 個(gè)節(jié)點(diǎn)的平衡樹(shù),最壞的查找時(shí)間復(fù)雜度也為 O(logn)。
那么我們?yōu)槭裁催€需要紅黑樹(shù)?
因?yàn)槲覀儾粌H僅需要關(guān)心查找的效率,還要考慮插入的效率。因?yàn)槠胶舛鏄?shù)要求每個(gè)節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的高度差至多等于1,這個(gè)要求非常嚴(yán)格,每次進(jìn)行插入/刪除數(shù)據(jù)的時(shí)候,幾乎都會(huì)破壞這個(gè)規(guī)則。在插入/刪除數(shù)據(jù)很頻繁的場(chǎng)景中,平衡二叉樹(shù)的性能就會(huì)受到很?chē)?yán)重的影響。所以我們需要紅黑樹(shù)。
紅黑樹(shù)的定義
- 查找:紅黑樹(shù)的查找和二叉排序樹(shù)一樣,不再贅述
- 添加:
所以我們發(fā)現(xiàn)每一次插入數(shù)據(jù)之后,如果不滿足紅黑樹(shù)的定義,就需要對(duì)紅黑樹(shù)的結(jié)構(gòu)進(jìn)行調(diào)整。兩種方式:旋轉(zhuǎn)和重新上色。所以我們?cè)贘ava源碼中找到了添加數(shù)據(jù)的方法,并分析這個(gè)方法。
/** 假如我們要往這個(gè)紅黑數(shù)里面添加6沒(méi)有調(diào)整過(guò)的樹(shù)是這樣的第一步,8和6兩個(gè)同為紅色結(jié)點(diǎn),所以改變顏色,8和14變黑,10變紅。對(duì)應(yīng)源碼中的情況1,當(dāng)插入結(jié)點(diǎn)的叔叔結(jié)點(diǎn)為紅,把父結(jié)點(diǎn)變黑,叔叔結(jié)點(diǎn)也變黑,再把爺爺結(jié)點(diǎn)變紅如果插入結(jié)點(diǎn)的爺爺結(jié)點(diǎn)是右孩子結(jié)點(diǎn),我們找到爺爺結(jié)點(diǎn)的父結(jié)點(diǎn),然后在這個(gè)結(jié)點(diǎn)上左旋,對(duì)應(yīng)源碼中的情況2下一步,右旋,10變黑,20變紅,對(duì)應(yīng)源碼中的情況3從源碼中看出,情況2是情況3的一種擴(kuò)展。情況4、5、6是1、2、3的鏡像,就不展開(kāi)了。
刪除也需要調(diào)整樹(shù)的結(jié)構(gòu),但是更復(fù)雜一些,我們看一下源碼中的方法。
/**刪除一共8種情況,1-4和5-8也是鏡像的,就不舉具體的例子了。TreeMap果然要比HashMap還要復(fù)雜的多,就算讀懂了 rebalance的方法,真正實(shí)現(xiàn)起來(lái)還是有很大的難度。以此為基礎(chǔ),下一篇我們嘗試一下手寫(xiě)HashMap。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的映射表map(平衡二叉树实现)_手动实现Java集合容器之TreeMap(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python 量化交易_基于Python
- 下一篇: fileinputstream_从Jav