1. 程式人生 > 實用技巧 >平衡二叉樹(AVL)原理解析與實現(C++)

平衡二叉樹(AVL)原理解析與實現(C++)

1. 簡介

1.1 定義

平衡二叉查詢樹:簡稱平衡二叉樹。在電腦科學中,AVL樹是最早被髮明的自平衡二叉查詢樹。在AVL樹中,任一節點對應的兩棵子樹的最大高度差為1,因此它也被稱為高度平衡樹。查詢、插入和刪除在平均和最壞情況下的時間複雜度都是O(logn)。增加和刪除元素的操作則可能需要藉由一次或多次樹旋轉,以實現樹的重新平衡。AVL 樹得名於它的發明者 G. M. Adelson-Velsky 和 Evgenii Landis,他們在1962年的論文《An algorithm for the organization of information》中公開了這一資料結構。

1.2 性質

一棵AVL樹有如下必要條件:

  • 它必須是二叉查詢樹。
  • 每個節點的左子樹和右子樹的高度差至多為1。

AVL樹的查詢、插入、刪除操作在平均和最壞的情況下都是O(logn),這得益於它時刻維護著二叉樹的平衡。如果我們需要查詢的集合本身沒有順序,在頻繁查詢的同時也經常的插入和刪除,AVL樹是不錯的選擇。不平衡的二叉查詢樹在查詢時的效率是很低的,因此,AVL如何維護二叉樹的平衡是我們的學習重點。

2. AVL的實現

2.1 結點結構

struct AVLNode {
	int data;
	AVLNode *lChild, *rChild;
	AVLNode(int a) {
		data = a;
		lChild = NULL;
		rChild = NULL;
	}
};
複製程式碼

2.2 平衡二叉樹的基本框架

class AVLTree {
public:
	AVLTree();
	~AVLTree();

	///從命令列接受資料建立AVL樹。使用其他方式建立原理一致
	void Create();

	///輸出該排序樹
	void Print();

	///先序遍歷輸出
	void PreOrder(AVLNode *p);
	///中序遍歷輸出。
	void MidOrder(AVLNode *p);

	///返回二叉樹高度
	int Height(AVLNode *p);

	///返回根節點
	AVLNode* getRoot() {
		return root;
	}

	///簡化版插入函式
	void Insert(const int x);

	///簡化版刪除函式
	void Remove(int x);

	///返回值為x的結點指標
	AVLNode* Search(int x);

	///返回子樹最大值結點的指標
	AVLNode* TreeMax;

	///返回子樹最小值結點的指標
	AVLNode* TreeMin(AVLNode* subTree);

private:
	///根節點
	AVLNode *root;

	///插入結點(因為對任一結點進行插入操作後都需要平衡操作,可能會改變該處的結點,
	///因此設定返回值記錄完成操作後此處的結點指標)
	AVLNode* Insert(AVLNode* subRoot, const int k);

	///刪除結點(視為在該結點為根節點的樹上進行刪除操作)
	AVLNode* Remove(AVLNode* subRoot, int x);

	///返回某個結點的平衡因子
	int BalanceFactor(AVLNode *p);

	///對某個結點進行平衡操作(根據平衡因子呼叫四種不同的旋轉操作)
	AVLNode* Balancee(AVLNode* subRoot);
	/*****************************
	          四種旋轉操作
	******************************/
	/// LL平衡旋轉(右單旋轉):在左孩子(L)的左子樹(L)上插入導致不平衡
	AVLNode* LL_Rotation(AVLNode *subRoot);

	/// RR平衡旋轉(左單旋轉):在右孩子(R)的右子樹(R)上插入導致不平衡
	AVLNode* RR_Rotation(AVLNode *subRoot);

	/****下面兩種情況可看作是對根節點和子節點進行上兩種旋轉操作的組合******/
	/// RL平衡旋轉(先右後左雙旋轉):在右孩子(R)的左子樹(L)上插入導致不平衡
	AVLNode* RL_Rotation(AVLNode *subRoot);

	/// LR平衡旋轉(先左後右雙旋轉): 在左孩子(L)的右子樹(R)上插入導致不平衡
	AVLNode* LR_Rotation(AVLNode *subRoot);

	///銷燬該樹
	void destroy(AVLNode* p);
};
複製程式碼

2.3 重要成員函式原理解析

2.3.1 四種旋轉操作函式

  在理解AVL旋轉之前,首先得知道以下幾個概念:
  1. AVL 樹節點的插入總是在葉子節點。
  2. AVL 樹在插入節點之前總是滿足平衡條件的。
  3. 插入新節點後有可能滿足平衡條件也有可能不滿足。
  4. 當不滿足平衡條件後,我們就需要對新的樹進行旋轉。

1) LL平衡旋轉(右單旋轉)

  由於在A的左孩子(L)的左子樹(L)上插入新結點,使原來平衡二叉樹變得不平衡,此時A的平衡因子由1增至2。
  LL型調整的一般形式如下圖1所示,表示在A的左孩子B的左子樹BL(不一定為空)中插入結點(圖中陰影部分所示)而導致不平衡( h 表示子樹的深度)。這種情況調整如下:
  ①將A的左孩子B提升為新的根結點 ;
  ②將原來的根結點A降為B的右孩子;
  ③各子樹按大小關係連線(BL和AR不變,BR調整為A的左子樹)。

             圖1 一般形式的LL型調整              圖2 LL旋轉例項

	/// LL平衡旋轉(右單旋轉)
	//在左孩子(L)的左子樹(L)上插入導致不平衡,需要向右旋轉一次實現平衡
	AVLNode* LL_Rotation(AVLNode *subRoot) {
		AVLNode* temp = subRoot->lChild;
		subRoot->lChild = temp->rChild;
		temp->rChild = subRoot;
		//完成旋轉操作之後,該處分支結點(原為subRoot)發生了變化,
		//因此要返回新的分支節點指標供其父節點更新孩子指標
		return temp;
	}
複製程式碼

2) RR平衡旋轉(左單旋轉)

  由於在A的右孩子(R)的右子樹(R)上插入新結點,使原來平衡二叉樹變得不平衡,此時A的平衡因子由-1變為-2。
  RR型調整的一般形式如下圖3所示,表示在A的右孩子B的右子樹BR(不一定為空)中插入結點(圖中陰影部分所示)而導致不平衡(h 表示子樹的深度)。這種情況調整如下:
  ①將A的右孩子B提升為新的根結點;
  ②將原來的根結點A降為B的左孩子;
  ③各子樹按大小關係連線(AL和BR不變,BL調整為A的右子樹)。

            圖3 一般形式的RR型調整             圖4 RR旋轉例項

	/// RR平衡旋轉(左單旋轉)
	//在右孩子(R)的右子樹(R)上插入導致不平衡,需要向左旋轉一次實現平衡
	AVLNode* RR_Rotation(AVLNode *subRoot) {
		AVLNode* temp = subRoot->rChild;
		subRoot->rChild = temp->lChild;
		temp->lChild = subRoot;
		//完成旋轉操作之後,該處分支結點(原為subRoot)發生了變化,
		//因此要返回新的分支節點指標供其父節點更新孩子指標
		return temp;
	}
複製程式碼

下面兩種情況可看作是對根節點和子節點進行上兩種旋轉操作的組合

RL平衡旋轉(先右後左雙旋轉)

  由於在A的右孩子(R)的左子樹(L)上插入新結點,使原來平衡二叉樹變得不平衡,此時A的平衡因子由-1變為-2。
  RL型調整的一般形式如下圖5所示,表示在A的右孩子B的左子樹(根結點為C,不一定為空)中插入結點(圖中兩個陰影部分之一)而導致不平衡( h 表示子樹的深度)。這種情況調整如下:
  ①將B的左孩子C提升為新的根結點;
  ②將原來的根結點A降為C的左孩子;
  ③各子樹按大小關係連線(AL和BR不變,CL和CR分別調整為A的右子樹和B的左子樹)。

            圖5 一般形式的RL型調整             圖6 RL旋轉例項

	/// RL平衡旋轉(先右後左雙旋轉)
	//在右孩子(R)的左子樹(L)上插入導致不平衡,需要先對分支結點的右孩子進行一次右旋(LL_Rotation),
	//再對分支結點進行一次左旋(RR_Rotation)
	AVLNode* RL_Rotation(AVLNode *subRoot) {
		//對subRoot右孩子結點LL旋轉後,更新subRoot右結點指標
		subRoot->rChild=LL_Rotation(subRoot->rChild);  
		return RR_Rotation(subRoot);//返回新的分支結點供原分支節點的父節點更新孩子指標
	}
複製程式碼

LR平衡旋轉(先左後右雙旋轉)

  由於在A的左孩子(L)的右子樹(R)上插入新結點,使原來平衡二叉樹變得不平衡,此時A的平衡因子由1變為2。
  LR型調整的一般形式如下圖7所示,表示在A的左孩子B的右子樹(根結點為C,不一定為空)中插入結點(圖中兩個陰影部分之一)而導致不平衡( h 表示子樹的深度)。這種情況調整如下:
  ①將B的左孩子C提升為新的根結點;
  ②將原來的根結點A降為C的右孩子;
  ③各子樹按大小關係連線(BL和AR不變,CL和CR分別調整為B的右子樹和A的左子樹)。

           圖7 一般形式的LR型調整            圖8 LR旋轉例項

	/// LR平衡旋轉(先左後右雙旋轉)
	//在左孩子(L)的右子樹(R)上插入導致不平衡,需要先對分支結點的左孩子進行一次左旋(RR_Rotation),
	//再對分支結點進行一次右旋(LL_Rotation)
	AVLNode* LR_Rotation(AVLNode *subRoot) {
		//對subRoot左結點RR旋轉後,更新subRoot左結點指標
		subRoot-> lChild = RR_Rotation(subRoot->lChild);  
		return LL_Rotation(subRoot);//返回新的分支結點供原分支節點的父節點更新孩子指標
	}
複製程式碼

2.3.2 返回結點的平衡因子

  平衡因子=左子樹高度-右子樹高度

	///返回某個節點的平衡因子
	int BalanceFactor(AVLNode *p) {
		return Height(p->lChild) - Height(p->rChild);
	}
複製程式碼

2.3.3 平衡某結點為根節點的子樹

  對某個結點進行平衡操作,需根據平衡因子分別呼叫上面實現的四種旋轉操作。

	///對某個結點進行平衡操作(根據平衡因子呼叫四種不同的旋轉操作)
	AVLNode* Balancee(AVLNode* subRoot) {
		int bf = BalanceFactor(subRoot);
		if (bf > 1) //左子樹更高
		{
			if (BalanceFactor(subRoot->lChild) > 0)
				//左孩子結點平衡因子>0說明新節點多在了左子樹上,因此呼叫LL_Rotation
				subRoot = LL_Rotation(subRoot);
			else
				//左孩子結點平衡因子<0說明新節點多在了右子樹上,因此呼叫LR_Rotation
				subRoot = LR_Rotation(subRoot);
		}
		else if (bf < -1) //右子樹更高
		{
			if (BalanceFactor(subRoot->rChild) > 0)
		        //右孩子結點平衡因子>0說明新節點多在了左子樹上,因此呼叫RL_Rotation
				subRoot = RL_Rotation(subRoot);
			else
			    //右孩子結點平衡因子<0說明新節點多在了右子樹上上,因此呼叫RR_Rotation
				subRoot = RR_Rotation(subRoot);
		}
		//對分支結點進行平衡操作後可能會更新該分支節點,故將新的分支結點返回供原父結點更新孩子指標
		return subRoot;
	}
複製程式碼

2.3.4 返回某結點為根節點的子樹高度

  使用遞迴求樹的高度

	///返回某結點為根節點的子樹高度
	int Height(AVLNode *p) {
		if (p == NULL)
			return 0;
		int i = Height(p->lChild);
		int j = Height(p->rChild);
		return i > j ? i + 1 : j + 1;
	}
複製程式碼

2.3.5 返回子樹最大/小值結點指標

  最大值位於樹的最右端,最小值位於樹的最左端。

	///返回子樹最大值結點的指標
	AVLNode* TreeMax(AVLNode* subTree) {
		if (!subTree)
			return NULL;
		while (subTree->rChild) {
			subTree = subTree->rChild;
		}
		return subTree;
	}
	///返回子樹最小值結點的指標
	AVLNode* TreeMin(AVLNode* subTree) {
		if (!subTree)
			return NULL;
		while (subTree->lChild) {
			subTree = subTree->lChild;
		}
		return subTree;
	}
複製程式碼

2.3.6 插入值為x的結點

  插入操作視為根據x和分支結點的比較結果,在不同的子樹上進行插入並進行平衡操作,並不斷更新該子樹根節點的父節點的孩子指標。

	///簡化版插入函式
	void Insert(const int x) {
		root = Insert(root, x);
	}
	///插入(視為在某結點為根節點的子樹上進行插入)
	//對子樹上進行插入操作後都需要平衡操作,可能會改變該子樹的根節點,
	//因此設定返回值記錄完成操作後子樹的根結點指標)
	AVLNode* Insert(AVLNode* subRoot, const int k) {
		if (subRoot == NULL) {
			subRoot = new AVLNode(k);
		}
		else if (k > subRoot->data) //需要在右子樹上插入新的結點
		{
			subRoot->rChild = Insert(subRoot->rChild, k);
			//在右子樹上插入結點後可能導致不平衡,故需要對右子樹進行平衡操作
			//而平衡操作可能會導致子樹根結點產生變化,故需更新當前的子樹根節點
			subRoot = Balancee(subRoot);
		}
		else if (k < subRoot->data) { //需要在左子樹上插入新的結點
			subRoot->lChild = Insert(subRoot->lChild, k);
			//和上面同理
			subRoot = Balancee(subRoot);
		}
		//將新的子樹根結點指標返回供原父節點更新孩子指標
		return subRoot;
	}
複製程式碼

2.3.7 建立二叉樹函式(從控制檯接收數值)

  從棧獲取,陣列中獲取等構造方式都是依次呼叫Insert()函式實現,因不是本文核心這裡不作列舉。

	///從命令列接受資料建立AVL樹。使用其他方式建立原理一致
	void Create() {
		cout << "input numbers to create AVL: " << endl;
		int temp;
		while (cin >> temp) {
			root=Insert(root, temp);
		}
		cout << "AVL建立完成!" << endl;
		Print();
	}
複製程式碼

2.3.8 刪除結點

  刪除操作是AVL實現中的重難點!
  本文通過對刪除結點所處的位置進行分類討論,使用遞迴的形式實現,刪除結點時需要分類討論的情況如下:

  • 情況1:要刪除的就是該樹的根節點
      1) 該樹的左右子樹都存在:
      > 左子樹高於右子樹,則根節點的值替換為其直接前驅的值,然後通過遞迴呼叫轉化為刪除其直接前驅(其位於左子樹上,也就意味著去降低左子樹高度)並平衡該根結點;
      > 右子樹高於左子樹,則根節點的值替換為其直接後繼的值,然後通過遞迴呼叫轉化為刪除其直接後繼(其位於右子樹上,也就意味著去降低右子樹高度) 並平衡該根結點;
      2) 只存在一顆子樹,使用孩子結點替換根節點,並平衡該結點;
      3) 左右子樹都不存在,根節點置為NULL;[ 2) ,3)程式碼實現時可以合併]
  • 情況2:要刪除的節點位於左子樹上則遞迴呼叫---即對根結點的左子樹上進行刪除操作,並平衡該子樹;
  • 情況3:要刪除的節點位於右子樹上---在根結點的右子樹上進行刪除操作,並平衡該該子樹;
	///簡化版刪除函式
	void Remove(int x) {
		root=Remove(root, x);
	}
	///刪除結點(視為在該結點為根節點的樹上進行刪除操作)
	AVLNode* Remove(AVLNode* subRoot, int x) {
		if (!Search(x)) {//不存在x的結點則直接返回
			cout << "不存在值為" << x << "的結點!" << endl;
			return root;
		}

		if (!root)  //root為空指標都直接返回NULL
			return root;
			
		if (subRoot->data == x)  //情況1:要刪除的就是該樹的根節點
		{
			if (subRoot->lChild && subRoot->rChild)//情況1.1:該樹的左右子樹都存在
			{
				if (BalanceFactor(subRoot)>0) 
				{
					//左子樹高於右子樹,則根節點的值替換為其直接前驅的值,然後轉化為刪除
					//其直接前驅(其位於左子樹上,也就意味著去降低左子樹高度)
					AVLNode *tmp = TreeMax(subRoot->lChild); //直接前驅就是左子樹的最大值
					subRoot->data = tmp->data;
					//遞迴呼叫Remove()刪除subRoot的左子樹上的前驅結點後,Remove()返回可能為
					//新的subRoot的左子樹根節點供subRoot更新左孩子結點((Remove()會呼叫Balance()函式平衡其操作的樹))。
					subRoot->lChild = Remove(subRoot->lChild, tmp->data);
				}
				else {
					//右子樹高於左子樹,則根節點的值替換為其直接後繼的值,
					//然後轉化為刪除其直接後繼(其位於右子樹上,也就意味著去降低右子樹高度)
					AVLNode *tmp = TreeMin(subRoot->rChild);
					subRoot->data = tmp->data;
					subRoot->rChild = Remove(subRoot->rChild, tmp->data);
				}
			}
			else //情況1.2:只存在一顆子樹或者都不存在
			{
				//直接將根節點更新為其孩子結點(都不存在則為NULL)
				AVLNode * tmp = subRoot;
				subRoot = (subRoot->lChild) ? (subRoot->lChild) : (subRoot->rChild);
				delete tmp;
				tmp = NULL;
			}
		}
		else if (x < subRoot->data) { //情況2:要刪除的節點位於左子樹上
			//遞迴呼叫,在subRoot的左子樹上進行刪除操作,並返回新的左子樹根節點供subRoot更新左孩子指標
			subRoot->lChild = Remove(subRoot->lChild, x);
			//在subRoot的左子樹上完成刪除操作後,可能導致該樹不平衡,故需要進行平衡操作並更新當前根節點
			if (BalanceFactor(subRoot) < -1)
				subRoot = Balancee(root);
		}
		else {//情況3:要刪除的節點位於右子樹上
			//遞迴呼叫,在subRoot的右子樹上進行刪除操作,並返回新的右子樹根節點供subRoot更新右孩子指標
			subRoot->rChild = Remove(subRoot->rChild, x);
			//在subRoot的右子樹上完成刪除操作後,可能導致該樹不平衡,故需要進行平衡操作並更新當前根節點
			if (BalanceFactor(subRoot) >1)
				subRoot = Balancee(subRoot);
		}
		//返回該樹當前根節點供其父節點更新孩子節點
		return subRoot;
	}
複製程式碼

2.3.9 返回結點值為x的結點指標

  依次和分支節點對比直到找到到值為x的結點.

	///返回值為x的結點指標
	AVLNode* Search(int x) {
		AVLNode *p = root;
		while (p) {
			if (p->data == x)
				break;
			else if (p->data < x)
				p = p->rChild;
			else
				p = p->lChild;
		}
		return p;
	}
複製程式碼

2.3.10 輸出二叉樹

  使用中序遍歷和先序遍歷輸出二叉樹。若對二叉樹的先序、中序、後續遍歷的遞迴和非遞迴演算法感興趣,請查閱:二叉樹成員函式程式碼實現及簡單原理分析

	///輸出該排序樹
	void Print() {
		cout << "中序遍歷為: ";
		MidOrder(root);
		cout << endl;
		cout << "先序遍歷為: ";
		PreOrder(root);
		cout << endl;
	}

	///先序遍歷輸出
	void PreOrder(AVLNode *p) {
		if (p != NULL) {
			cout << p->data << " ";
			PreOrder(p->lChild);
			PreOrder(p->rChild);
		}
	}
	///中序遍歷輸出。
	void MidOrder(AVLNode *p) {
		if (p != NULL) {
			MidOrder(p->lChild);
			cout << p->data << " ";
			MidOrder(p->rChild);
		}
	}
複製程式碼

2.3.11 銷燬二叉樹

使用遞迴形式完成銷燬操作

	///銷燬該樹
	void destroy(AVLNode* p) {
		if (p) {
			destroy(p->lChild);
			destroy(p->rChild);
			delete p;
		}
	}
複製程式碼

2.4 完整程式碼

/*******平衡二叉樹(AVL)***********/
#pragma once
#include<iostream>
using namespace std;

///平衡二叉樹結點結構
struct AVLNode {
	int data;
	AVLNode *lChild, *rChild;
	AVLNode(int a) {
		data = a;
		lChild = NULL;
		rChild = NULL;
	}
};

class AVLTree {
public:
	AVLTree() {
		root = NULL;
	}
	~AVLTree() {
		destroy(root);
	}

	///從命令列接受資料建立AVL樹。使用其他方式建立原理一致
	void Create() {
		cout << "input numbers to create AVL: " << endl;
		int temp;
		while (cin >> temp) {
			root=Insert(root, temp);
		}
		cout << "AVL建立完成!" << endl;
		Print();
	}

	///返回某結點為根節點的子樹高度
	int Height(AVLNode *p) {
		if (p == NULL)
			return 0;
		int i = Height(p->lChild);
		int j = Height(p->rChild);
		return i > j ? i + 1 : j + 1;
	}

	///輸出該排序樹
	void Print() {
		cout << "中序遍歷為: ";
		MidOrder(root);
		cout << endl;
		cout << "先序遍歷為: ";
		PreOrder(root);
		cout << endl;
	}

	///先序遍歷輸出
	void PreOrder(AVLNode *p) {
		if (p != NULL) {
			cout << p->data << " ";
			PreOrder(p->lChild);
			PreOrder(p->rChild);
		}
	}
	///中序遍歷輸出
	void MidOrder(AVLNode *p) {
		if (p != NULL) {
			MidOrder(p->lChild);
			cout << p->data << " ";
			MidOrder(p->rChild);
		}
	}

	///簡化版插入函式
	void Insert(const int x) {
		root = Insert(root, x);
	}

	///簡化版刪除函式
	void Remove(int x) {
		root=Remove(root, x);
	}

	///返回值為x的結點指標
	AVLNode* Search(int x) {
		AVLNode *p = root;
		while (p) {
			if (p->data == x)
				break;
			else if (p->data < x)
				p = p->rChild;
			else
				p = p->lChild;
		}
		return p;
	}

	///返回子樹最大值結點的指標
	AVLNode* TreeMax(AVLNode* subTree) {
		if (!subTree)
			return NULL;
		while (subTree->rChild) {
			subTree = subTree->rChild;
		}
		return subTree;
	}

	///返回子樹最小值結點的指標
	AVLNode* TreeMin(AVLNode* subTree) {
		if (!subTree)
			return NULL;
		while (subTree->lChild) {
			subTree = subTree->lChild;
		}
		return subTree;
	}

private:
	///根節點
	AVLNode *root;

	///插入(視為在某結點為根節點的子樹上進行插入)
	//對子樹上進行插入操作後都需要平衡操作,可能會改變該子樹的根節點,
	//因此設定返回值記錄完成操作後子樹的根結點指標)
	AVLNode* Insert(AVLNode* subRoot, const int k) {
		if (subRoot == NULL) {
			subRoot = new AVLNode(k);
		}
		else if (k > subRoot->data) //需要在右子樹上插入新的結點
		{
			subRoot->rChild = Insert(subRoot->rChild, k);
			//在右子樹上插入結點後可能導致不平衡,故需要對右子樹進行平衡操作
			//而平衡操作可能會導致子樹根結點產生變化,故需更新當前的子樹根節點
			subRoot = Balancee(subRoot);
		}
		else if (k < subRoot->data) { //需要在左子樹上插入新的結點
			subRoot->lChild = Insert(subRoot->lChild, k);
			//和上面同理
			subRoot = Balancee(subRoot);
		}
		//將新的子樹根結點指標返回供原父節點更新孩子指標
		return subRoot;
	}

	///刪除結點(視為在該結點為根節點的樹上進行刪除操作)
	AVLNode* Remove(AVLNode* subRoot, int x) {
		if (!Search(x)) {//不存在x的結點則直接返回
			cout << "不存在值為" << x << "的結點!" << endl;
			return root;
		}

		if (!root)  //root為空指標都直接返回NULL
			return root;

		if (subRoot->data == x)  //情況1:要刪除的就是該樹的根節點
		{
			if (subRoot->lChild && subRoot->rChild)//情況1.1:該樹的左右子樹都存在
			{
				if (BalanceFactor(subRoot)>0) 
				{
					//左子樹高於右子樹,則根節點的值替換為其直接前驅的值,然後轉化為刪除
					//其直接前驅(其位於左子樹上,也就意味著去降低左子樹高度)
					AVLNode *tmp = TreeMax(subRoot->lChild); //直接前驅就是左子樹的最大值
					subRoot->data = tmp->data;
					//遞迴呼叫Remove()刪除subRoot的左子樹上的前驅結點後,Remove()返回可能為
					//新的subRoot的左子樹根節點供subRoot更新左孩子結點((Remove()會呼叫Balance()函式平衡其操作的樹))。
					subRoot->lChild = Remove(subRoot->lChild, tmp->data);
				}
				else {
					//右子樹高於左子樹,則根節點的值替換為其直接後繼的值,
					//然後轉化為刪除其直接後繼(其位於右子樹上,也就意味著去降低右子樹高度)
					AVLNode *tmp = TreeMin(subRoot->rChild);
					subRoot->data = tmp->data;
					subRoot->rChild = Remove(subRoot->rChild, tmp->data);
				}
			}
			else //情況1.2:只存在一顆子樹或者都不存在
			{
				//直接將根節點更新為其孩子結點(都不存在則為NULL)
				AVLNode * tmp = subRoot;
				subRoot = (subRoot->lChild) ? (subRoot->lChild) : (subRoot->rChild);
				delete tmp;
				tmp = NULL;
			}
		}
		else if (x < subRoot->data) { //情況2:要刪除的節點位於左子樹上
			//遞迴呼叫,在subRoot的左子樹上進行刪除操作,並返回新的左子樹根節點供subRoot更新左孩子指標
			subRoot->lChild = Remove(subRoot->lChild, x);
			//在subRoot的左子樹上完成刪除操作後,可能導致該樹不平衡,故需要進行平衡操作並更新當前根節點
			if (BalanceFactor(subRoot) < -1)
				subRoot = Balancee(root);
		}
		else {//情況3:要刪除的節點位於右子樹上
			//遞迴呼叫,在subRoot的右子樹上進行刪除操作,並返回新的右子樹根節點供subRoot更新右孩子指標
			subRoot->rChild = Remove(subRoot->rChild, x);
			//在subRoot的右子樹上完成刪除操作後,可能導致該樹不平衡,故需要進行平衡操作並更新當前根節點
			if (BalanceFactor(subRoot) >1)
				subRoot = Balancee(subRoot);
		}
		//返回該樹當前根節點供其父節點更新孩子節點
		return subRoot;
	}

	///返回某個節點的平衡因子
	int BalanceFactor(AVLNode *p) {
		return Height(p->lChild) - Height(p->rChild);
	}

	///對某個結點進行平衡操作(根據平衡因子呼叫四種不同的旋轉操作)
	AVLNode* Balancee(AVLNode* subRoot) {
		int bf = BalanceFactor(subRoot);
		if (bf > 1) //左子樹更高
		{
			if (BalanceFactor(subRoot->lChild) > 0)
				//左孩子結點平衡因子>0說明新節點多在了左子樹上,因此呼叫LL_Rotation
				subRoot = LL_Rotation(subRoot);
			else
				//左孩子結點平衡因子<0說明新節點多在了右子樹上,因此呼叫LR_Rotation
				subRoot = LR_Rotation(subRoot);
		}
		else if (bf < -1) //右子樹更高
		{
			if (BalanceFactor(subRoot->rChild) > 0)
		        //右孩子結點平衡因子>0說明新節點多在了左子樹上,因此呼叫RL_Rotation
				subRoot = RL_Rotation(subRoot);
			else
			    //右孩子結點平衡因子<0說明新節點多在了右子樹上上,因此呼叫RR_Rotation
				subRoot = RR_Rotation(subRoot);
		}
		//對分支結點進行平衡操作後可能會更新該分支節點,故將新的分支結點返回供原父結點更新孩子指標
		return subRoot;
	}
	/*****************************
	          四種旋轉操作
	******************************/
	/// LL平衡旋轉(右單旋轉)
	//在左孩子(L)的左子樹(L)上插入導致不平衡,需要向右旋轉一次實現平衡
	AVLNode* LL_Rotation(AVLNode *subRoot) {
		AVLNode* temp = subRoot->lChild;
		subRoot->lChild = temp->rChild;
		temp->rChild = subRoot;
		//完成旋轉操作之後,該處分支結點(原為subRoot)發生了變化,
		//因此要返回新的分支節點指標供其父節點更新孩子指標
		return temp;
	}
	/// RR平衡旋轉(左單旋轉)
	//在右孩子(R)的右子樹(R)上插入導致不平衡,需要向左旋轉一次實現平衡
	AVLNode* RR_Rotation(AVLNode *subRoot) {
		AVLNode* temp = subRoot->rChild;
		subRoot->rChild = temp->lChild;
		temp->lChild = subRoot;
		//完成旋轉操作之後,該處分支結點(原為subRoot)發生了變化,
		//因此要返回新的分支節點指標供其父節點更新孩子指標
		return temp;
	}
	/***********下面兩種情況可看作是對根節點和子節點進行上兩種旋轉操作的組合*****************/
	/// RL平衡旋轉(先右後左雙旋轉)
	//在右孩子(R)的左子樹(L)上插入導致不平衡,需要先對分支結點的右孩子進行一次右旋(LL_Rotation),
	//再對分支結點進行一次左旋(RR_Rotation)
	AVLNode* RL_Rotation(AVLNode *subRoot) {
		//對subRoot右孩子結點LL旋轉後,更新subRoot右結點指標
		subRoot->rChild=LL_Rotation(subRoot->rChild);  
		return RR_Rotation(subRoot);//返回新的分支結點供原分支節點的父節點更新孩子指標
	}
	/// LR平衡旋轉(先左後右雙旋轉)
	//在左孩子(L)的右子樹(R)上插入導致不平衡,需要先對分支結點的左孩子進行一次左旋(RR_Rotation),
	//再對分支結點進行一次右旋(LL_Rotation)
	AVLNode* LR_Rotation(AVLNode *subRoot) {
		//對subRoot左結點RR旋轉後,更新subRoot左結點指標
		subRoot-> lChild = RR_Rotation(subRoot->lChild);  
		return LL_Rotation(subRoot);//返回新的分支結點供原分支節點的父節點更新孩子指標
	}

	///銷燬該樹
	void destroy(AVLNode* p) {
		if (p) {
			destroy(p->lChild);
			destroy(p->rChild);
			delete p;
		}
	}
};
複製程式碼

3 測試程式碼(main.cpp)

   ///8.平衡二叉樹
    AVLTree avl;
	avl.Create();
	int iterTimes=9;
	cout << "刪除測試:" << endl;
	for (int i = 1; i < iterTimes; i++) {
		avl.Remove(i);
		cout <<"刪除 "<< i << " 後: "<<endl;
		avl.Print();
	}
	cout << endl;
複製程式碼

輸入:8 3 1 4 7 5 2 6 ^Z //^Z為ctrl+z,表示輸入中止
輸出:

4 參考資料

  1. 平衡二叉樹:blog.csdn.net/isunbin/art…
  2. STL原始碼筆記(18)—平衡二叉樹AVL(C++封裝+模板):blog.csdn.net/zhangxiao93…
  3. 百度百科·平衡樹:baike.baidu.com/item/平衡樹/76…

作者:德槿
連結:https://juejin.cn/post/6844904006033080333
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。