国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼)

這篇具有很好參考價值的文章主要介紹了【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言

前面對map/multimap/set/multiset進行了簡單的介紹,在其文檔介紹中發(fā)現(xiàn)。
這幾個容器有個共同點是:其底層都是按照二叉搜索樹來實現(xiàn)的,但是二叉搜索樹有其自身的缺陷,假如往樹中插入的元素有序或者接近有序,二叉搜索樹就會退化成單支樹,時間復雜度會退化成O(N),因此map、set等關聯(lián)式容器的底層結構是對二叉樹進行了平衡處理,即采用平衡樹來實現(xiàn)。

那這篇文章我們就重點來學習一種平衡搜索二叉樹——AVL樹

1. AVL樹的概念

二叉搜索樹雖可以提升查找的效率,但如果數(shù)據(jù)有序或接近有序時二叉搜索樹將退化為單支樹,查找元素相當于在順序表中搜索元素,效率低下。
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

因此,兩位俄羅斯的數(shù)學家G.M.Adelson-Velskii和E.M.Landis在1962年發(fā)明了一種解決上述問題的方法:

當向二叉搜索樹中插入新結點后,如果能保證每個結點的左右子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),即可降低樹的高度,使整棵搜索樹達到一個相對平衡的狀態(tài),從而減少平均搜索長度。

那大家思考一個問題:為什么是每個結點左右子樹高度之差的絕對值不超過1,為什么不能是兩邊一樣高,高度差為0呢?

??,如果能達到左右子樹完全一樣高固然是最好的,但是關鍵在于有些情況不可能實現(xiàn)兩邊絕對平衡!
比如
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
兩個結點、4個結點的情況,當然肯定不止這些。大家看這種情況能實現(xiàn)兩邊完全平衡嗎?
是不行的,無法達到完全平衡。

所以,什么是平衡二叉樹呢?

一棵AVL樹或者是空樹,或者是具有以下性質的二叉搜索樹:

  1. 它的左右子樹都是AVL樹
  2. 左右子樹高度之差(簡稱平衡因子,一般是右子樹-左子樹的高度差,當然左-右也可以)的絕對值不超過1(-1/0/1)
    ps:圖中每個結點旁邊的數(shù)字就是其對應的平衡因子【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
    如果一棵二叉搜索樹是高度平衡的(即滿足任何一個結點的平衡因子都在[-1, 0, 1]這個范圍內),它就是AVL樹。如果它有n個結點,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2?n),搜索的時間復雜度O( l o g 2 n ) log_2 n) log2?n)

2. AVL樹結構的定義

那我們這里以KV模型的結構來講解,當然本質都是一樣的

首先我們來寫一下結點的結構

template <class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K,V>* _right;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;//balance factor(平衡因子)

	AVLTreeNode(const pair<K,V>& kv)
		:_right(nullptr)
		,_left(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};

這里我們給結點增加一個_parent指針指向它的父親結點,方便我們后續(xù)進行某些操作,當然帶來方便的同時我們也需要去維護每個結點的_parent指針,相應也帶來了一些麻煩。
這個后面我們實現(xiàn)的時候大家就會體會到。

然后AVL樹的結構

template <class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	//成員函數(shù)
private:
	Node* _root = nullptr;
};

那然后我們來寫一下插入吧

3. 插入(僅僅是插入過程)

AVL樹就是在二叉搜索樹的基礎上引入了平衡因子來控制樹的相對平衡,因此AVL樹也可以看成是二叉搜索樹。

所以插入的邏輯其實跟搜索二叉樹是一樣的,不同的地方在于平衡二叉樹插入之后如果整棵二叉樹或者其中某些子樹不平衡了我們要對插入的結點進行調整使得它重新變的平衡,那這個我們后面單獨講。

由于插入的邏輯我們之前已經(jīng)講過了,所以這里我就直接上代碼了(這里我們選擇非遞歸)

不過需要注意的是我們這里插入新結點之后還要鏈接_parent指針。

bool insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (kv.first < cur->_kv.first)
		{
			parent = cur;
			ccur = cur->_left;
		}
		else if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(kv);
	if (kv.first < parent->_kv.first)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	//鏈接父親指針
	cur->_parent = parent;
	//更新平衡因子
	//...
	return true;
}

大家看著代碼再過一遍這個插入的過程。

那現(xiàn)在我問大家,AVL樹的插入寫到這里就完了嗎?
??,如果是普通的搜索樹,這就完事了,但是,對于AVL樹來說,還遠遠沒有結束。

4. 平衡因子的更新

為了實現(xiàn)平衡二叉樹,我們引入了一個新的概念,不知道大家還記不記得是啥?

??,就是我們上面提到的平衡因子。
再來回顧一下什么是平衡因子?
一個結點的平衡因子就是它的左右子樹的高度差,一般是右子樹減左子樹的高度(我們這里的講解也統(tǒng)一以右子樹-左子樹的高度作為平衡因子)。

4.1 為什么要更新平衡因子?

那大家想一下:我們在AVL樹中插入了一個新結點之后,會不會影響到樹中結點的平衡因子?

毋庸置疑,這當然是會的!
因為一旦插入了新的結點,整棵樹的高度或者某些子樹的高度必然會發(fā)生變化,那樹的高度發(fā)生變化,必然會影響與之關聯(lián)的結點的平衡因子。

所以,插入了新結點之后,導致某些樹的高度發(fā)生變化,我們要更新平衡因子。

??,那平衡因子會變化,這沒啥說的,但是為啥變化了我就得更新呢?不更新行不行?

答案是不行。
為什么呢?
因為上面我們說了,如果一棵二叉搜索樹是AVL樹,那么它必須滿足任何一個結點的平衡因子都在[-1, 0, 1]這個范圍內。
而現(xiàn)在插入新結點會導致平衡因子變化,那么更新之后,某些結點的平衡因子可能就不在[-1, 0, 1]這個正常范圍內了。
那他就不是一棵AVL樹了,所以我們才要更新平衡因子,以此來判斷這個樹還是否是一棵AVL樹。
如果不是了,即有結點的平衡因子不在正常范圍內了,那這棵樹的平衡就受到影響了,那我們就需要對新插入的結點進行調整,使他變回AVL樹。
當然如果插入之后平衡沒有受到影響,就不需要調整了。

那調整結點的事,我們后面再說,現(xiàn)在先談一談,插入新結點后,如何更新平衡因子!

4.2 如何更新平衡因子?

那首先大家思考一個問題,插入一個新結點之后,可能會影響到哪些結點的平衡因子?

是不是影響的肯定是它的祖先啊。
因為新插入的結點在它祖先的子樹上,那它祖先的子樹高度發(fā)生變化,平衡因子必然也會發(fā)生變化。
但是會影響所有的祖先嗎?
不一定!可能只影響一部分。
比如:【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
所以具體影響了幾個祖先要根據(jù)具體情況具體分析。

那既然要更新,我就來研究一下更新的規(guī)律:

那這個規(guī)律呢,其實也很容易得出:
因為平衡因子的計算是右子樹高度-左子樹高度嘛。
所以,對于新結點的父親來說:

  1. 如果插入在了右子樹,那么父親的平衡因子就要++
  2. 如果插入在了左子樹,那么父親的平衡因子就要- -


【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這時候我們的parent指針的作用就體現(xiàn)出來了

4.3 parent更新后,是否需要繼續(xù)往上更新?

那父親結點的平衡因子更新完之后,還要不要繼續(xù)往上更新呢?

首先parent肯定要更新,因為插入之后它的子樹的高度變了。
所以大家先想一下,什么情況下parent更新完之后還要繼續(xù)往上更新parent的祖先?
??,是不是取決于parent所在的這棵子樹的高度有沒有發(fā)生變化啊。

  1. 如果插入之后parent這棵子樹的高度沒有變化,那就不會影響parent再往上結點(即parent的祖先)的平衡因子,就不需要往上繼續(xù)更新了
    【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
  2. 如果插入之后parent這棵子樹的高度發(fā)生了變化,那parent的平衡因子更新完成后就需要繼續(xù)往上更新
    【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

那我們分析一下其實分為這三種情況:

  1. 如果parent的平衡因子更新之后為1或-1,則parent這棵樹的高度發(fā)生變化,需要繼續(xù)向上更新

為什么呢?為什么parent的平衡因子變成1或-1,它的高度就變了呢?

我們剛才是不是分析過,插入一個新結點,它的parent的平衡因子是怎么變化的,是不是要么-1,要么+1啊。
那它現(xiàn)在更新之后變成了1或者-1,能夠說明什么?
是不是說明它更新之前的平衡因子一定是0啊,0的話說明他兩邊高度是平衡的,而現(xiàn)在插入之后變?yōu)?或-1,說明右邊或者左邊高了,因此高度肯定是變化了,那就要繼續(xù)往上更新。
那繼續(xù)往上更新是不是又是同樣的邏輯啊(我們只需將結點往上走,下次循環(huán)自然會進行同樣的處理,后面代碼實現(xiàn)出來大家會更清晰)。

那可能是-2或者2加一減一之后變成-1或1?。?/p>

不可能,因為AVL樹的平衡因子的范圍都是在[-1, 0, 1]內的。

  1. parent的平衡因子更新之后為2或-2

如果是2或-2呢?要繼續(xù)往上更新嗎?

??,如果是2或-2,那已經(jīng)不在平衡因子的正常范圍內了,那就說明當前parent所在的這棵子樹已經(jīng)不平衡了!?。。ㄍǔ0堰@棵樹叫做最小不平衡子樹)
那還往上更新個屁啊,是不是就要去調整結點是這棵最小不平衡子樹重變平衡
那怎么調整呢,要進行旋轉,具體怎么做后面再講。
那旋轉之后它的高度其實就恢復到插入之前了,也就不需要再繼續(xù)往上更新了。

  1. parent的平衡因子更新之后為0

那為0的話需要繼續(xù)更新嗎?

為0當然就不需要了。
為什么呢?
大家想,更新之后為0的話,是不是說明插入之前它的平衡因子為1或者-1啊,然后我們在左邊插入了一個結點或者是右邊,然后它的平衡因子就變成了0
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
那他的高度是不是沒有發(fā)生變化啊,所以不需要繼續(xù)更新,也不需要調整,插入就結束了。

4.4 平衡因子更新代碼實現(xiàn)

我們來寫一下代碼:

因為不知道要向上更新幾次,所以肯定是一個循環(huán)。
那循環(huán)什么時候結束?
通過我們上面的分析,它可能向上更新幾次就停止了,但是不排除有可能一直更新直到根結點
比如這種情況【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
所以整個循環(huán)的結束條件是這樣的
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
根結點沒有父親(根結點的parent為空),所以如果parent不為空,就有可能要一直向上更新。
然后循環(huán)體里面的內容就按照我們上面的分析寫就行了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

//更新平衡因子
while (parent)
{
	//更新parent的平衡因子
	if (cur == parent->_right)
	{
		parent->_bf++;
	}
	else
	{
		parent->_bf--;
	}
	//判斷是否需要繼續(xù)向上更新,需要就往上走等待下次循環(huán)更新,
	//如果不平衡了就進行處理,不需要處理不需要調整就break
	if (parent->_bf == 1 || parent->_bf == -1)
	{
		parent = parent->_parent;
		cur = cur->_parent;
	}
	else if(parent->_bf == 2 || parent->_bf == -2)
	{
		//進行旋轉調整
		//...
		break;
	}
	else if (parent->_bf == 0)
	{
		break;
	}
	else
	{
		//非正常情況
		assert(false);
	}
}

那接下來我們就來重點講一下對于不平衡的情況如何進行調整,即AVL樹的旋轉

5. AVL樹的旋轉

如果在一棵原本是平衡的AVL樹中插入一個新節(jié)點,可能造成不平衡,此時必須調整樹的結構,使之平衡化。

根據(jù)節(jié)點插入位置的不同,AVL樹的旋轉分為四種,接下來我們將一 一進行學習

5.1 新節(jié)點插入較高右子樹的右側—右右:左單旋

我們先來學習第一種旋轉——左單旋。

什么情況要進行左單旋

那什么樣的情況要進行左單旋呢?

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
就是上圖的這種情況。

大家對照著圖,我們來分析一下:

首先大家可能有疑問

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
圖里面的a、b、c是啥???
??,我們這里給的是一個抽象圖,a、b、c分別代表三棵高度為h的AVL子樹,這里的h可以為任何整數(shù)值(所以h取不同的值,這里具體的情況是有很多種的,不過不用擔心,針對這一類情況,我們的處理是統(tǒng)一的)。

那我們看這個圖

原本30這棵AVL樹(當然實際中他也可能是一棵子樹,子樹的話上面就還有結點)處在平衡的狀態(tài),右子樹比左子樹高1,然后現(xiàn)在我們在它的右子樹的右側c這里插入新結點,然后它的高度變成h+1。
注意我們討論的情況是插入之后它的高度+1,如果高度不變的話也不需要調整了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
還有就是如果插入之后,c的高度雖然+1了,但是c這棵子樹直接變的不平衡了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這兩種情況不是我們現(xiàn)在要討論的。
我們現(xiàn)在討論的情況就是插入之后c的高度變成h+1了,并且平衡因子需要向上更新影響到30,導致30這棵樹不平衡
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
比如這樣的
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
那針對這種情況我們要進行左單旋處理(不論這里的高度h對應是幾,這種情況都是左單旋處理)。
大家可以自己多畫幾個h為不同高度的圖。

如何進行左單旋

那左單旋處理是怎么做呢?

現(xiàn)在我們插入之后是這樣的
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
現(xiàn)在30這個結點的平衡因子是不在正常范圍內的,這棵樹是不平衡的,右邊高,所以要對30這棵樹進行左單旋,怎么左單旋呢?
??,其實兩步就搞定了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
相當于把30往左邊向下旋轉,所以叫左單旋。
大家看,進行了左單旋之后,這棵樹是不是就重新變成AVL樹,達到平衡狀態(tài)了啊,樹的高度也降下去了。
為什么這樣旋轉,大家看60的左子樹比60小,比30大,所以可以做30的右子樹,然后30整棵樹都比60小,所以可以做60的左子樹。
當然降高度是一方面,在使它變平衡的同時是不是也要保持它依舊是一顆搜索二叉樹啊,因為AVL樹就是平衡的搜索二叉樹嘛(大家可以看我們旋轉過程選擇的孩子都是滿足搜索樹的大小關系的)。
大家可以把h換成實際的數(shù)字,畫一個圖,然后進行一下插入、左單旋,再理解一下這個過程。
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這是抽象圖的一個完整過程。

左單旋代碼實現(xiàn)

那然后我們來寫一下左單旋的代碼:

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
旋轉的時候傳要旋轉的子樹的根結點即可。
然后我們可以把需要操作到的幾個結點獲取一下
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
然后,按照上面講的思路進行旋轉就行了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
ps:解釋一下為什么起名subR,sub是subtree (子樹)的縮寫
對照著圖,大家看一下這樣寫對不對。

??,這樣寫是有問題的:

第一個問題——沒有處理結點的_parent指針
我們上面實現(xiàn)的時候給結點增加了一個指向其父親的指針_parent,方便我們更新平衡因子的時候往上走,但是代價就是需要我們去維護這個指針。
所以,旋轉之后要更新_parent指針
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
然后呢,還不行
第二個問題——subRL 可能為空
為什么?
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
看圖,如果h等于0的話subRL是不是就是空啊。
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
所以加個判斷。
接著,第三個問題——parent上面可能還有結點(即旋轉的是子樹)
我們上面分析的時候說了,我們這里旋轉的可能是一整棵樹,也可能是一棵樹中的子樹。
所以如果是子樹的話,上面還有結點
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這樣我們旋轉之后上面結點的指向就不對了
所以我們也要處理一下,判斷它是不是子樹,然后進行不同的處理
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
最后,還有一個問題——旋轉之后要更新一下平衡因子
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
至此,我們的左單旋才算完成

//左單旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if(subRL)
		subRL->_parent = parent;

	//先保存一下parent->_parent,因為下面會改它
	Node* pparent = parent->_parent;

	subR->_left = parent;
	parent->_parent = subR;

	//若pparent為空則證明旋轉的是一整棵樹,因為根結點的_parent為空
	if (pparent == nullptr)
	{
		//subR是新的根
		_root = subR;
		_root->_parent == nullptr;
	}
	//若pparent不為空,則證明旋轉的是子樹,parent上面還有結點
	else
	{
		//讓pparent指向子樹旋轉之后新的根
		if (pparent->_left == parent)
		{
			pparent->_left = subR;
		}
		else
		{
			pparent->_right = subR;
		}
		//同時也讓新的根指向pparent
		subR->_parent = pparent;
	}
	//旋轉完更新平衡因子
	parent->_bf = subR->_bf = 0;
}

什么時候調用左單旋

那我們代碼寫好了,什么時候調用呢?

我們觀察圖會發(fā)現(xiàn)
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
如果parent的平衡因子是2,subR(對應我們在更新平衡因子的那個循環(huán)里就是cur)的平衡因子是1,此時要進行的就是左單旋
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

if (parent->_bf == 2 && cur->_bf == 1)
{
	RotateL(parent);
}

5.2 新節(jié)點插入較高左子樹的左側—左左:右單旋

接著我們看第二種旋轉——右單旋

什么情況要進行右單旋

那右單旋又適用于哪些情況呢呢?

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
同樣的我們這里討論的情況是插入之后a的高度要發(fā)生變化,且會影響到當前這棵樹(當然它可以是一棵子樹)根結點的平衡因子,導致整棵樹不平衡,這時我們可以用右單旋解決。

如何進行右單旋

那右單旋又該如何操作呢?

也是兩步
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
相當于把30往右邊向上旋轉,所以叫右單旋。
30的右子樹比30大,比60小,所以可以做60的左子樹,然后60整棵樹都比30大,所以可以做30的右子樹。
這樣這棵樹就重新變平衡了,30成為了新的根結點。

右單旋代碼實現(xiàn)

那我們來寫一下右單旋的代碼

那寫了上面左單旋的代碼,再寫右單旋的話應該就比較輕松了,需要注意的點還是那幾個
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
對照著圖,我們來寫一下,這里我就不做過多解釋了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

//右單旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = parent->_right;

	//旋轉并更新_parent指針
	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;
	
	//先保存一下parent->_parent,因為下面會改它
	Node* pparent = parent->_parent;

	//旋轉并更新_parent指針
	subL->_right = parent;
	parent->_parent = subL;

	//若parent等于_root則證明旋轉的是一整棵樹(這也是一種判斷方法)
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		//讓pparent指向子樹旋轉之后新的根
		if (parent == pparent->_left)
		{
			pparent->_left = subL;
		}
		else
		{
			pparent->_right = subL;
		}
		//同時也讓新的根指向pparent
		subL->_parent = pparent;
	}
	subL->_bf = parent->_bf = 0;
}

什么時候調用右單旋

那右單旋什么時候調用呢?

來看圖
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
??,我們看到如果parent的平衡因子為-2,subL(cur)的平衡因子為-1,要調用的就是右單旋
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

5.3 新節(jié)點插入較高左子樹的右側—左右:先左單旋再右單旋(左右雙旋)

再來看第三種旋轉——左右雙旋

什么情況進行左右雙旋

看這張圖
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這里給的是在b插入,在c插入當然也是左右雙旋,但是插入之后平衡因子的更新會有一些不同,后面會提到。
這還是抽象圖,我們來畫幾個具象圖看一下
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

如何進行左右雙旋

首先要知道對于這種情況,我們如果只進行左或者右的單旋是解決不了問題的

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
大家看這種情況插入之后根結點90是-2,-2就表明左邊高嘛。
那左邊高的話如果我們進行右旋可以變平衡嗎?
那對它右旋之后是這樣的
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這是不是還不平衡啊,現(xiàn)在變成右邊高了

那要進行雙旋,怎么做呢?

上面已經(jīng)說了針對這種情況要進行的是左右雙旋,那顧名思義就是先進行一個左單旋(對根的左子樹),再進行一個右單旋(對根)
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
然后就平衡了,其實我們能發(fā)現(xiàn)它就是把60推上去做根,然后60的左右子樹分給30的右子樹和90的左子樹。
為什么不能直接右單旋,因為大家看他原來不是一個單純的左邊高
插入之后類似這樣一個形狀
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
首先第一步的左單旋相當于把它變成單純的左邊高
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
然后在進行一次右單旋,就平衡了。

左右雙旋代碼實現(xiàn)

那左右雙旋的代碼怎么寫?是不是直接復用左右單旋的代碼就行了

那就先調用左旋,再調用右旋就行了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
但是左右雙旋麻煩的地方其實在于平衡因子的調節(jié)。
我們上面提到插入在b和c它們最后平衡因子更新不同
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
能看到旋轉之后它們的平衡因子更新是不一樣的。
那如何判斷在b插入還是在c插入呢?
??,大家看圖,不同位置的插入,插入之后60這個結點的平衡因子是不同的。
那除此之外,h為0的時候,其實平衡因子的更新又有所不同
如果h==0的話
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
它旋轉是這樣的
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
所以,平衡因子的更新這里我們要分三種情況
我們還是記錄一下這三個結點,方便操作
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
然后我們補充一下平衡因子更新的代碼,不同情況更新不同的值
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

//左右雙旋
void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	RotateL(parent->_left);
	RotateR(parent);

	//更新平衡因子
	if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

什么時候調用左右雙旋

看圖

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

else if (parent->_bf == -2 && cur->_bf == 1)
{
	RotateLR(parent);
}

5.4 新節(jié)點插入較高右子樹的左側—右左:先右單旋再左單旋(右左雙旋)

什么情況進行右左雙旋

那我們來看一下右左雙旋適用于哪些情況?

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
當然插入到b這棵樹上也是可以的。
同樣的高度h不同,就會產(chǎn)生很多不同的情況,但是沒關系,這要是這種情況,我們就可以統(tǒng)一處理

如何進行右左雙旋

那就還是兩次單旋嘛:

這里就是首先進行一次右單旋(對根的右子樹),然后再進行一次左單旋(對根)
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
最后就平衡了。
其實根上面學的左右雙旋是同樣的道理:
這樣的情況只旋一次是不能達到平衡的,所以第一次其實是把它變成純粹的右邊高,然后再進行一次左單旋就平衡了。
那最終的結果就相當于把60推上去做根,然后60的左右子樹分別分給30的右子樹和90的左子樹。
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

右左雙旋代碼實現(xiàn)

那右左單旋的話我們可以是可以直接復用左單旋和右單旋的,但是,同樣的道理,我們還是需要對雙旋之后的平衡因子分不同的情況進行更新處理:

與左右雙旋一樣,還是三種情況,不同情況平衡因子的更新不同,通過插入之后subRL的平衡因子區(qū)分三種情況

  1. 就是我們上面分析的,在c插入
    【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
  2. 在b插入
    【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
  3. h等于0情況下的插入
    【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

那對應的代碼就是
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

//右左雙旋
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RotateR(parent->_right);
	RotateL(parent);

	//更新平衡因子
	if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

什么時候調用右左雙旋

很容易看出來:

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
當根結點的平衡因子為2,cur為-1的時候調用的是右左雙旋
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法

5.5 總結

假如以pParent為根的子樹不平衡,即pParent的平衡因子為2或者-2,分以下情況考慮

  1. pParent的平衡因子為2,說明pParent的右子樹高,設pParent的右子樹的根為SubR
    當SubR的平衡因子為1時,執(zhí)行左單旋
    當SubR的平衡因子為-1時,執(zhí)行右左雙旋
  2. pParent的平衡因子為-2,說明pParent的左子樹高,設pParent的左子樹的根為SubL
    當SubL的平衡因子為-1是,執(zhí)行右單旋
    當SubL的平衡因子為1時,執(zhí)行左右雙旋
    【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
    旋轉完成后,原pParent為根的子樹個高度降低,已經(jīng)平衡,不需要再向上更新。

6. AVL樹的測試

AVL樹是在二叉搜索樹的基礎上加入了平衡性的限制,因此要測試AVL樹,可以分兩步:

6.1 驗證其為二叉搜索樹

我們插入一些數(shù)據(jù),如果中序遍歷可得到一個有序的序列,就說明為二叉搜索樹

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
我們定義一棵AVL樹,然后插入一些數(shù)據(jù)中序遍歷一下。
寫一個中序遍歷
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
然后我們運行一下
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
??,沒什么問題,是有序的。

6.2 驗證其為平衡樹

那如何驗證它是否平衡呢?

我們可以去計算高度,如果每一個結點左右子樹的高度差的絕對值不超過1,就證明它是平衡的。
為什么不用平衡因子判斷呢?
首先,不是所有的AVL樹的實現(xiàn)里面都有平衡因子的,只是我們這里采用了平衡因子,這是AVL樹的一種實現(xiàn)方法而已。
其次,我們不敢保證我們自己寫到代碼計算出來的平衡因子一定是正確的。

所以,我們來寫一個通過高度差來判斷是否平衡的函數(shù)

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這個比較簡單,我就不過多解釋了

然后我們測試一下

先判斷一下剛才的那棵樹【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
??,是平衡的。
我們再來看一個比較特殊的場景
{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
這個是一個右左雙旋的場景
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
沒什么問題。
如果我們不調整的話
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
那它應該就是不平衡了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
沒問題。

然后呢,我們還可以做一件事情

6.3 判斷平衡因子的更新是否正確

怎么判斷:

很簡單,計算一下高度差,看他和平衡因子相不相等就行了
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
再來測試一下
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
沒有問題,還是平衡。

6.4 大量隨機數(shù)構建AVL樹進行測試

上面的測試數(shù)據(jù)量比較小,且不夠隨機

下面我們生成一些隨機數(shù)來構建AVL樹,測試一下

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
10萬個隨機數(shù),先來試一下
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
沒有問題,10萬個隨機數(shù)構建也沒有出現(xiàn)錯誤的情況,依然是平衡的。
來,100萬個隨機數(shù)
【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法
依舊沒問題。

7. 查找

然后AVL樹的查找那就跟搜索二叉樹是一樣的,我們這里就不講了,大家可以看之前搜索二叉樹的文章。

8. AVL樹的刪除(了解)

AVL樹的刪除操作我們不做重點講解,大家了解一下即可,因為這個不是特別重要,面試一般也不會考到。

AVL樹的刪除操作主要分為以下幾個步驟:

  1. 執(zhí)行二叉搜索樹的刪除操作
  2. 更新平衡因子:如果刪除之后影響到了上面結點的平衡因子,就要從被刪除節(jié)點的父節(jié)點向上更新受影響的平衡因子。
  3. 檢查所有的平衡因子,如果存在不正常的平衡因子,則要對相應的樹進行調整,使它恢復平衡。
  4. 重復步驟2和步驟3,直至到達根節(jié)點或不需要進一步調整為止。

9. AVL樹的性能

AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節(jié)點的左右子樹高度差的絕對值都不超過1,這樣可以保證查詢時高效的時間復雜度,即 l o g 2 ( N ) log_2 (N) log2?(N)。
但是如果要對AVL樹做一些結構修改的操作,性能非常低下。
比如:插入時要維護其絕對平衡,旋轉的次數(shù)比較多,更差的是在刪除時,有可能一直要讓旋轉持續(xù)到根的位置。
因此:如果需要一種查詢高效且有序的數(shù)據(jù)結構,而且數(shù)據(jù)的個數(shù)為靜態(tài)的(即不會改變),可以考慮AVL樹,但一個結構經(jīng)常修改,就不太適合。

10. 源碼

10.1 AVLTree.h

#pragma once
#include <assert.h>

template <class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K,V>* _right;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;//balance factor

	AVLTreeNode(const pair<K,V>& kv)
		:_right(nullptr)
		,_left(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};

template <class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			//cout << "root:"<<_root->_kv.first << endl;
			return true;
		}
		
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//鏈接父親指針
		cur->_parent = parent;

		//更新平衡因子
		while (parent)
		{
			//更新parent的平衡因子
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			//判斷是否需要繼續(xù)向上更新,需要就往上走等待下次循環(huán)更新,
			//如果不平衡了就進行處理,不需要處理不需要調整就break
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if(parent->_bf == 2 || parent->_bf == -2)
			{
				//根據(jù)實際情況進行相應的旋轉調整
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				//旋轉完結束,就不需要再往上更新了
				break;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			else
			{
				//非正常情況
				assert(false);
			}
		}
		//cout << "root:" << _root->_kv.first << endl;
		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
private:
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;
		int rightH = _TreeHeight(root->_right);
		int leftH = _TreeHeight(root->_left);

		if (rightH - leftH != root->_bf)
		{
			cout << root->_kv.first << "結點平衡因子更新錯誤" << endl;
			return false;
		}

		return abs(rightH - leftH) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int RightH = _TreeHeight(root->_left);
		int leftH = _TreeHeight(root->_right);
		return RightH > leftH ? RightH + 1 : leftH + 1;
	}
	//左單旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//旋轉并更新_parent指針
		parent->_right = subRL;
		if(subRL)
			subRL->_parent = parent;

		//先保存一下parent->_parent,因為下面會改它
		Node* pparent = parent->_parent;

		//旋轉并更新_parent指針
		subR->_left = parent;
		parent->_parent = subR;

		//若pparent為空則證明旋轉的是一整棵樹,因為根結點的_parent為空
		if (pparent == nullptr)
		{
			//subR是新的根
			_root = subR;
			_root->_parent = nullptr;
		}
		//若pparent不為空,則證明旋轉的是子樹,parent上面還有結點
		else
		{
			//讓pparent指向子樹旋轉之后新的根
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			//同時也讓新的根指向pparent
			subR->_parent = pparent;
		}
		//旋轉完更新平衡因子
		parent->_bf = subR->_bf = 0;
	}

	//右單旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//旋轉并更新_parent指針
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		
		//先保存一下parent->_parent,因為下面會改它
		Node* pparent = parent->_parent;

		//旋轉并更新_parent指針
		subL->_right = parent;
		parent->_parent = subL;

		//若parent等于_root則證明旋轉的是一整棵樹(這也是一種判斷方法)
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//讓pparent指向子樹旋轉之后新的根
			if (parent == pparent->_left)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			//同時也讓新的根指向pparent
			subL->_parent = pparent;
		}
		subL->_bf = parent->_bf = 0;
	}
	//左右雙旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		//更新平衡因子
		if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	//右左雙旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		//更新平衡因子
		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

10.2 Test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "AVLTree.h"
#include <time.h>


void AVLTest1()
{
	//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	//int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	//int arr[] = { 1,2,3,4,5,6,7,8,9,1 };
	int arr[] = { 95,47,32,29,7,7,2,50,74,30 };

	AVLTree<int, int> t1;
	for (auto e : arr)
	{
		//cout << e << endl;
		t1.Insert(make_pair(e, e));
		t1.InOrder();
		if (!t1.IsBalance())
		{
			break;
		}
	}
	//t1.InOrder();
	/*if (t1.IsBalance())
	{
		cout << "平衡" << endl;
	}
	else
	{
		cout << "不平衡" << endl;
	}*/
}

void AVLTest2()
{
	srand(time(nullptr));
	const int N = 1000000;
	AVLTree<int, int> t;
	for (int i = 0; i < N; ++i)
	{
		int x = rand();
		t.Insert(make_pair(x, x));
	}
	if (t.IsBalance())
	{
		cout << "平衡" << endl;
	}
	else
	{
		cout << "不平衡" << endl;
	}
}

int main()
{
	AVLTest2();
	return 0;
}

【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼),高階數(shù)據(jù)結構(C++),C++,數(shù)據(jù)結構,二叉樹,AVL樹,c++,算法文章來源地址http://www.zghlxwxcb.cn/news/detail-643062.html

到了這里,關于【高階數(shù)據(jù)結構】AVL樹詳解(圖解+代碼)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 數(shù)據(jù)結構:AVL樹講解(C++)

    數(shù)據(jù)結構:AVL樹講解(C++)

    普通二叉搜索樹: 二叉搜索樹 二叉搜索樹雖可以縮短查找的效率,但如果 數(shù)據(jù)有序或接近有序普通的二叉搜索樹將退化為單支樹 ,查找元素相當于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數(shù)學家G.M.Adelson-Velskii和E.M.Landis在1962年發(fā)明了一種解決上述問題的方法

    2024年02月04日
    瀏覽(21)
  • 數(shù)據(jù)結構(C++) : AVL樹 實現(xiàn)篇

    數(shù)據(jù)結構(C++) : AVL樹 實現(xiàn)篇

    目錄 1.AVL樹引入 ? (1)二叉搜索樹缺點 ? (2)AVL樹簡介 ? ? [1]問題的解決 ? ? [2]AVL樹的性質 2.AVL樹的插入旋轉操作 ? (1)術語解釋 ? (2)左單旋 ? ? [1]插入到右側的左邊 ? ? [2]插入到右側的右邊 ? (3)右單旋 ? ? [1]插入到左側的左邊 ? ? [2]插入到左側的右邊 ? (4)左右雙旋 ? ?

    2024年02月05日
    瀏覽(20)
  • [數(shù)據(jù)結構 C++] AVL樹的模擬實現(xiàn)

    [數(shù)據(jù)結構 C++] AVL樹的模擬實現(xiàn)

    問題引入: 在上一篇文章中,我們提到了二叉搜索樹在插入時,可能會形成單邊樹,會降低二叉搜索的性能。因此我們需要平衡二叉搜索樹,降低二叉搜索樹的高度,使得二叉搜索樹趨于一顆完全二叉樹的樣子,這樣就可以提高二叉搜索樹的性能。本篇文章就來介紹一種平衡

    2024年02月03日
    瀏覽(19)
  • 數(shù)據(jù)結構07:查找[C++][平衡二叉排序樹AVL]

    數(shù)據(jù)結構07:查找[C++][平衡二叉排序樹AVL]

    圖源:文心一言 考研筆記整理1w+字,小白友好、代碼可跑,請小伙伴放心食用~~???? 第1版:查資料、寫B(tài)UG、畫導圖、畫配圖~???? 參考用書: 王道考研《2024年 數(shù)據(jù)結構考研復習指導》 參考用書配套視頻: 7.3_2 平衡二叉樹_嗶哩嗶哩_bilibili 特別感謝: ?Chat GPT老師、文心

    2024年02月11日
    瀏覽(46)
  • 【高階數(shù)據(jù)結構】哈希表詳解

    【高階數(shù)據(jù)結構】哈希表詳解

    上一篇文章我們學習了STL中unordered系列容器的使用,并且提到,unordered系列容器的效率之所以比較高(尤其是查找),是因為它底層使用了哈希結構,即哈希表。 那這篇文章,我們就來學習一下哈希表 順序結構以及平衡樹中,元素關鍵碼與其存儲位置之間沒有對應的關系,

    2024年02月11日
    瀏覽(21)
  • 【高階數(shù)據(jù)結構】紅黑樹詳解

    【高階數(shù)據(jù)結構】紅黑樹詳解

    這篇文章我們再來學習一種平衡搜索二叉樹—— 紅黑樹 紅黑樹和AVL樹都是常見的自平衡二叉搜索樹,它們都可以用于高效地支持插入、刪除和查找等操作。雖然它們都能夠保持樹的平衡性,但在不同的應用場景下,紅黑樹和AVL樹有各自的優(yōu)勢和適用性。 紅黑樹(Red-Black Tr

    2024年02月12日
    瀏覽(29)
  • 【高階數(shù)據(jù)結構】Map 和 Set(詳解)

    【高階數(shù)據(jù)結構】Map 和 Set(詳解)

    (???(??? )??,我是 Scort 目前狀態(tài):大三非科班啃C++中 ??博客主頁:張小姐的貓~江湖背景 快上車??,握好方向盤跟我有一起打天下嘞! 送給自己的一句雞湯??: ??真正的大師永遠懷著一顆學徒的心 作者水平很有限,如果發(fā)現(xiàn)錯誤,可在評論區(qū)指正,感謝?? ????

    2024年01月23日
    瀏覽(59)
  • 【高階數(shù)據(jù)結構】手撕哈希表(萬字詳解)

    【高階數(shù)據(jù)結構】手撕哈希表(萬字詳解)

    (???(??? )??,我是 Scort 目前狀態(tài):大三非科班啃C++中 ??博客主頁:張小姐的貓~江湖背景 快上車??,握好方向盤跟我有一起打天下嘞! 送給自己的一句雞湯??: ??真正的大師永遠懷著一顆學徒的心 作者水平很有限,如果發(fā)現(xiàn)錯誤,可在評論區(qū)指正,感謝?? ????

    2024年01月19日
    瀏覽(26)
  • 【數(shù)據(jù)結構】堆詳解!(圖解+源碼)

    【數(shù)據(jù)結構】堆詳解!(圖解+源碼)

    ?? 嶼小夏 : 個人主頁 ??個人專欄 : 數(shù)據(jù)結構解析 ?? 莫道桑榆晚,為霞尚滿天! 堆是一種基本而強大的數(shù)據(jù)結構。本文將深入探討堆的概念、原理以及實現(xiàn)。 普通的二叉樹是不適合用數(shù)組來存儲的,因為可能會存在大量的空間浪費。而完全二叉樹更適合使用順序結構存

    2024年02月05日
    瀏覽(19)
  • C++數(shù)據(jù)結構之平衡二叉搜索樹(一)——AVL的實現(xiàn)(zig與zag/左右雙旋/3+4重構)

    C++數(shù)據(jù)結構之平衡二叉搜索樹(一)——AVL的實現(xiàn)(zig與zag/左右雙旋/3+4重構)

    本文是介紹眾多平衡二叉搜索樹(BBST)的第一篇——介紹AVL樹。故先來引入BBST的概念。由于上一篇介紹的二叉搜索樹(BST)在極度退化的情況下,十分不平衡,不平衡到只朝一側偏,成為一條鏈表,復雜度可達 O ( n ) O(n) O ( n ) ,所以我們要在“平衡”方面做一些約束,以防

    2024年02月13日
    瀏覽(27)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包