摘要:可以看到,一次左單旋將右側(cè)子樹的高度減小了,而左側(cè)子樹的高度增加了。如圖,對(duì)進(jìn)行右單旋,需要左子樹的右子樹的高度小于等于左子樹的高度,否則不能達(dá)到平衡的效果,只是把不平衡性從左邊轉(zhuǎn)移到了右邊。
AVL樹
普通二叉搜索樹可能出現(xiàn)一條分支有多層,而其他分支卻只有幾層的情況,如圖1所示,這會(huì)導(dǎo)致添加、移除和搜索樹具有性能問題。因此提出了自平衡二叉樹的概念,AVL樹(阿德爾森-維爾斯和蘭迪斯樹)是自平衡二叉樹的一種,AVL樹的任一子節(jié)點(diǎn)的左右兩側(cè)子樹的高度之差不超過1,所以它也被稱為高度平衡樹。
要將不平衡的二叉搜索樹轉(zhuǎn)換為平衡的AVL樹需要對(duì)樹進(jìn)行一次或多次旋轉(zhuǎn),旋轉(zhuǎn)方式分為左單旋、右單旋、左-右雙旋、右-左雙旋。
左單旋對(duì)某一節(jié)點(diǎn)B(圖2)做左單旋,處理過程相當(dāng)于,斷開B與父節(jié)點(diǎn)A的連接,將B的右子節(jié)點(diǎn)D與A連接,將B作為D的左子節(jié)點(diǎn),將D的左子節(jié)點(diǎn)E作為B的右子節(jié)點(diǎn)。以圖1的二叉樹為例,對(duì)鍵值為15的節(jié)點(diǎn)做左單旋,首先斷開15與11的連接,再將20與11連接,將15作為20的左子節(jié)點(diǎn),最后將18作為15的右子節(jié)點(diǎn);可以想象為以15為中心做了一定的逆時(shí)針旋轉(zhuǎn)。結(jié)果如圖3。
再看圖2,根據(jù)搜索二叉樹的性質(zhì),肯定有D>B>A,E>B,因此旋轉(zhuǎn)過后,能夠保證 右子節(jié)點(diǎn) > 父節(jié)點(diǎn) > 左子節(jié)點(diǎn),不會(huì)破壞樹的結(jié)構(gòu)。
可以看到,一次左單旋將右側(cè)子樹的高度減小了1,而左側(cè)子樹的高度增加了1。實(shí)現(xiàn)代碼如下:
function roateLeft(AvlNode) { var node = AvlNode.right; // 保存右子節(jié)點(diǎn) AvlNode.right = node.left; // node的左子節(jié)點(diǎn)連接到AvlNode成為其右子節(jié)點(diǎn) node.left = AvlNode; // AvlNode連接到node成為其左子節(jié)點(diǎn) return node; // 返回node,連接到AvlNode最初的父節(jié)點(diǎn) }右單旋
右單旋與左單選類似,以某一節(jié)點(diǎn)B(圖4)做右單旋,首先斷開B與其父節(jié)點(diǎn)A的連接,將B的左子節(jié)點(diǎn)C與A連接,將C的右子節(jié)點(diǎn)F作為B的左子節(jié)點(diǎn)。同樣的,因?yàn)橛蠧>A,B>F>C,因此旋轉(zhuǎn)過后,不會(huì)破壞樹的結(jié)構(gòu)。可以看到,一次右單旋使節(jié)點(diǎn)的左側(cè)子樹高度減小了1,而右側(cè)子樹的高度增加了1。
實(shí)現(xiàn)代碼如下:
function roateRight(AvlNode) { var node = AvlNode.left; // 保存左子節(jié)點(diǎn) AvlNode.left = node.right; // 將node的右子節(jié)點(diǎn)連接到AvlNode成為其左子節(jié)點(diǎn) node.right = AvlNode; // AvlNode連接到node,成為其右子節(jié)點(diǎn) return node; // 返回node連接到AvlNode最初的父節(jié)點(diǎn) }左-右雙旋
左單旋、右單旋在某些情況下是不能達(dá)到平衡樹的目的的。如圖4,對(duì)B進(jìn)行右單旋,需要左子樹C的右子樹F的高度小于等于左子樹E的高度,否則不能達(dá)到平衡的效果,只是把不平衡性從左邊轉(zhuǎn)移到了右邊。圖5演示了這種情況。同樣的,左單旋也有這個(gè)問題。
因此為了達(dá)到目的,需要先對(duì)旋轉(zhuǎn)節(jié)點(diǎn)的左子節(jié)點(diǎn)做左單旋,再對(duì)旋轉(zhuǎn)節(jié)點(diǎn)做右單旋。如圖6所示,先對(duì)節(jié)點(diǎn)B的左子節(jié)點(diǎn)C做左單旋,可以看到,這個(gè)操作,相當(dāng)于將節(jié)點(diǎn)C的不平衡性從右側(cè)轉(zhuǎn)移到了左側(cè),從而滿足了上述右單旋的條件;最后再對(duì)B節(jié)點(diǎn)做右單旋操作,最終達(dá)到了平衡的目的。
實(shí)現(xiàn)代碼如下:
function roateLeftRight(AvlNode) { AvlNode.right = roateLeft(AvlNode.right); // 對(duì)右子節(jié)點(diǎn)做左單旋 return roateRight(AvlNode); // 做右單旋 }右-左雙旋
同理,如圖2,對(duì)B進(jìn)行左單旋時(shí),需要右子樹D的右子樹F的高度大于等于左子樹E的高度,否則需要進(jìn)行雙旋;即先對(duì)B的右子節(jié)點(diǎn)D做右單旋,再對(duì)B做左單旋。實(shí)現(xiàn)代碼如下:
function roateRightLeft(AvlNode) { AvlNode.left = roateRight(AvlNode.left); // 對(duì)左子節(jié)點(diǎn)做右單旋 return roateLeft(AvlNode); // 做左單旋 }實(shí)現(xiàn)樹的平衡
首先實(shí)現(xiàn)獲取樹高度的函數(shù):
function getAvlTreeHeight(node) { if (node == null) { // node不存在返回0 return 0; } else { var leftHeight = getAvlTreeHeight(node.left); var rightHeight = getAvlTreeHeight(node.right); // 返回左子樹、右子樹中的最大高度 return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1; } }
實(shí)現(xiàn)平衡樹的函數(shù):
function balance(node) { if (node == null) { return node; } // 左子樹高度比右子樹高度大1以上 if (getAvlTreeHeight(node.left) - getAvlTreeHeight(node.right) > 1) { if (getAvlTreeHeight(node.left.left) >= getAvlTreeHeight(node.left.right)) { // 如果左子樹的左子樹高度大于等于左子樹的右子樹高度 // 直接進(jìn)行右單旋 node = roateRight(node); } else { // 否則需要右-左雙旋 node = roateRightLeft(node); } // 右子樹高度比左子樹高度大1以上 } else if (getAvlTreeHeight(node.right) - getAvlTreeHeight(node.left) > 1) { if (getAvlTreeHeight(node.right.right) >= getAvlTreeHeight(node.right.left)) { // 如果右子樹的右子樹高度大于等于右子樹的左子樹高度 // 直接進(jìn)行左單旋 node = roateLeft(node); } else { // 否則需要左-右雙旋 node = roateLeftRight(node); } } return node; }
在二叉搜索樹的基礎(chǔ)上,每次插入節(jié)點(diǎn),都需要做一次樹的平衡處理:
var insertNode = function(node, newNode){ if (newNode.key < node.key){ if (node.left === null){ node.left = newNode; // 插入節(jié)點(diǎn)后,做樹的平衡處理 node.left = balance(node.left); } else { insertNode(node.left, newNode); } } else { if (node.right === null){ node.right = newNode; // 插入節(jié)點(diǎn)后,做樹的平衡處理 node.right = balance(node.right); } else { insertNode(node.right, newNode); } } }
綜上,一顆自平衡AVL樹的原理及實(shí)現(xiàn)就完成了。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86807.html
摘要:為了解決這類問題,我們進(jìn)行自平衡樹的學(xué)習(xí)。自平衡樹常見有兩種樹和紅黑樹。自平衡樹準(zhǔn)備知識(shí)節(jié)點(diǎn)的高度和平衡因子節(jié)點(diǎn)高度從節(jié)點(diǎn)到任意子節(jié)點(diǎn)的彼岸的最大值。 前面介紹了二叉樹和二叉樹搜索樹的創(chuàng)建和使用,接下來我們繼續(xù)學(xué)習(xí)關(guān)于樹的更多知識(shí)。BST存在一個(gè)問題,就是當(dāng)我們多次添加節(jié)點(diǎn)數(shù),有可能造成一種情況,樹的一條邊可能會(huì)非常深,有非常多的層,而另一條分支卻只有幾層。當(dāng)我們需要進(jìn)行添加、移除和搜...
摘要:原文博客地址學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)四樹知乎專欄簡(jiǎn)書專題前端進(jìn)擊者知乎前端進(jìn)擊者簡(jiǎn)書博主博客地址的個(gè)人博客人之所能,不能兼?zhèn)?,棄其所短,取其所長(zhǎng)。通常子樹被稱作左子樹和右子樹。敬請(qǐng)期待數(shù)據(jù)結(jié)構(gòu)篇最后一篇文章學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)五圖參考文章樹數(shù)據(jù)結(jié)構(gòu)二叉樹 前言 總括: 本文講解了數(shù)據(jù)結(jié)構(gòu)中的[樹]的概念,盡可能通俗易懂的解釋樹這種數(shù)據(jù)結(jié)構(gòu)的概念,使用javascript實(shí)現(xiàn)了樹,如有紕漏,歡迎批評(píng)指正。 ...
摘要:是棧,它繼承于。滿二叉樹除了葉結(jié)點(diǎn)外每一個(gè)結(jié)點(diǎn)都有左右子葉且葉子結(jié)點(diǎn)都處在最底層的二叉樹。沒有鍵值相等的節(jié)點(diǎn)。這是數(shù)據(jù)庫(kù)選用樹的最主要原因。 在我們學(xué)習(xí)Java的時(shí)候,很多人會(huì)面臨我不知道繼續(xù)學(xué)什么或者面試會(huì)問什么的尷尬情況(我本人之前就很迷茫)。所以,我決定通過這個(gè)開源平臺(tái)來幫助一些有需要的人,通過下面的內(nèi)容,你會(huì)掌握系統(tǒng)的Java學(xué)習(xí)以及面試的相關(guān)知識(shí)。本來是想通過Gitbook的...
摘要:節(jié)點(diǎn)的構(gòu)造函數(shù)默認(rèn)為其初始化都是。二叉排序樹插入插入節(jié)點(diǎn)只要遵循一個(gè)原則就好大與就向中插入,小于就向插入。初始化數(shù)據(jù)樹現(xiàn)在來試試實(shí)例化一個(gè)來看看效果。 JavaScript 數(shù)據(jù)結(jié)構(gòu)篇 之 BST 前言 有段時(shí)間沒有寫文章了,整個(gè)人感覺變得有點(diǎn)懶散了,大家可不要向我一樣哦~今天開始 seaconch 打算開始記錄 JavaScript 數(shù)據(jù)結(jié)構(gòu)的學(xué)習(xí)經(jīng)歷。至此,開始。 課本:《學(xué)習(xí)J...
閱讀 935·2021-11-08 13:22
閱讀 2863·2021-09-29 09:45
閱讀 2839·2021-09-09 11:52
閱讀 2271·2019-08-30 13:20
閱讀 3757·2019-08-29 13:28
閱讀 1375·2019-08-29 12:32
閱讀 2736·2019-08-29 11:10
閱讀 1655·2019-08-26 13:34