1. 程式人生 > >查詢——平衡二叉樹的實現(程式碼超詳細註釋)

查詢——平衡二叉樹的實現(程式碼超詳細註釋)

    既然你搜索到了這篇文章,那麼平衡二叉樹的作用想必心中已經清楚了,我們接下來就直接來談談程式碼...

目錄

知識準備  

進階講解    

程式碼實現

謝謝閱讀

知識準備  

    啥?你又不知道,真拿你沒辦法,給你一篇講的不錯的文章:

《大話資料結構》片段

進階講解    

    喂,看完別走呀,我再講點進階的知識,我們知道,平衡二叉樹的實現過程中最頭疼的就是實現旋轉操作,然而,我們可以換一種思維,我們不再去注意旋轉這個過程,而直接看重旋轉後的結果,旋轉後的結果無非是下面這種形式:

平衡二叉樹旋轉後的結果

即三個節點和四棵子樹這種形式,而這裡的三個節點分別是失衡節點及其子節點和孫節點(失衡方向上的),四棵子樹則為這三個節點的孩子,無論對於左旋操作,右旋操作,左旋-右旋操作,右旋-左旋操作,它們旋轉後的結果都可以轉化成這種形式,不用考慮旋轉的過程大大節省了我們的腦細胞,而這種重構的方式的名字也名如其人——3+4重構

。如果對於3+4操作還有疑惑,可以在接下來的程式碼中理解。

    在平衡二叉樹中,我們還需要注意,對於節點刪除操作,會出現失衡傳播現象,即區域性失衡你把它重平衡後,它的祖先節點可能又失衡了,所以要我們要不斷向上檢查是否還有失衡的節點。

程式碼實現

有了以上的準備,我們就可以上史上最詳細註釋的程式碼了:

//BinTree.h
#pragma once
//二叉樹模板
//SJ2050
#include <stack>

//二叉樹節點的模板定義
template <class ElemType>
struct BinTreeNode
{
	ElemType data;	//節點的資料

	BinTreeNode<ElemType> *parent;	//父節點指標
	BinTreeNode<ElemType> *leftChild;	//左孩子指標
	BinTreeNode<ElemType> *rightChild;	//右孩子指標

	unsigned int height;	//樹高
	//int bf;	//平衡因子,這裡為左子樹的高度減去右子樹的高度(廢棄)
};

//二叉樹的模板定義
template <class ElemType>
class BinTree
{
public:
	BinTree();	//建構函式,進行初始化的操作
	~BinTree();	//解構函式,進行銷燬工作

	virtual void Print()=0;	//將二叉樹中的內容打印出來
	virtual BinTreeNode<ElemType>* Search(ElemType data)=0;	//搜尋節點函式
	virtual bool Insert(ElemType data)=0;	//插入節點函式
	virtual bool Delete(ElemType data) = 0;	//刪除節點函式
	virtual void UpdateHeight(BinTreeNode<ElemType> &node);	//更新樹高

protected:
	BinTreeNode<ElemType> *root;	//根節點指標
};

//二叉樹建構函式的定義
template <class ElemType>
BinTree<ElemType>::BinTree()
{
	this->root = nullptr;	//將根節點初始化為空
}

//二叉樹解構函式的定義
template <class ElemType>
BinTree<ElemType>::~BinTree()
{
	stack<BinTreeNode<ElemType>*> stack;	//使用STL中的棧
	BinTreeNode<ElemType> *p = this->root;	//p指標先指向二叉樹的根節點

	if (p != nullptr)
	{
		stack.push(p);	//將根節點壓入棧
		while (!stack.empty())	//直至棧中無元素為止
		{
			p = stack.top();	//p指向棧頂元素
			stack.pop();	//棧彈出一個元素,即少一個元素
			if (p->leftChild != nullptr)
			{
				stack.push(p->leftChild);	//將左孩子節點壓入棧
			}
			if (p->rightChild != nullptr)
			{
				stack.push(p->rightChild);	//將右孩子節點壓入棧
			}
			
			delete p;	//刪除p指向的節點
		}
	}
	this->root = nullptr;	//將樹的根節點置空
}

//函式功能:更新樹中節點的樹高
//函式引數:開始更新的節點的引用
//函式返回值:void
template <class ElemType>
void BinTree<ElemType>::UpdateHeight(BinTreeNode<ElemType> &node)
{	//該函式自底向上更新各節點的樹高
	BinTreeNode<ElemType> *p = &node;
	
	while (p != nullptr)
	{
		if (p->leftChild == nullptr)
		{	//當該節點無左孩子時
			p->height = p->rightChild == nullptr ? 1 : p->rightChild->height + 1;
		}
		else if (p->rightChild == nullptr)
		{	//當該節點無右孩子時
			p->height = p->leftChild == nullptr ? 1 : p->leftChild->height + 1;
		}
		else
		{	//當該節點有左右孩子時
			p->height = p->leftChild->height > p->rightChild->height ? \
				p->leftChild->height + 1 : p->rightChild->height + 1;
		}
		p = p->parent;	//自底而上更新樹高
	}
}

以上為二叉樹類的模板

//AVL.h
#pragma once
//平衡二叉樹模板
//SJ2050
#include "BinTree.h"

#define OK 1
#define FALSE 0

//平衡二叉樹類模板定義
template <class ElemType>
class AVL : public BinTree<ElemType>
{
public:
	void Print();	//將二叉樹中的內容打印出來
	BinTreeNode<ElemType>* Search(ElemType data);	//搜尋節點函式
	bool Insert(ElemType data);	//插入節點函式
	bool Delete(ElemType data);	//刪除節點函式
	
private:
	void Rotate(BinTreeNode<ElemType> &unbalancedNode);	//旋轉函式
	void Connect34(BinTreeNode<ElemType> *a, BinTreeNode<ElemType> *b, BinTreeNode<ElemType> *c,\
		BinTreeNode<ElemType> *T0, BinTreeNode<ElemType> *T1, BinTreeNode<ElemType> *T2,\
		BinTreeNode<ElemType> *T3);	//3+4重構函式
	int CalculateBF(BinTreeNode<ElemType> &node);	//計算失衡值
	void PrintOut(BinTreeNode<ElemType> *beginNode);	//採用中序遍歷對二叉樹進行列印
};

//函式功能:搜尋待查詢的節點,並返回其位置(供使用者呼叫)
//函式引數:待查詢的值
//函式返回值:待查詢的節點的指標或該節點應該出現位置的父節點的指標,若樹為空返回nullptr
template <class ElemType>
BinTreeNode<ElemType>* AVL<ElemType>::Search(ElemType data)	//搜尋節點函式
{
	if (this->root == nullptr)
	{	//當樹還為空時
		return nullptr;
	}

	BinTreeNode<ElemType> *x = this->root;	//x節點
	BinTreeNode<ElemType> *p = x->parent;		//p為x的父節點
	
	while (x != nullptr && x->data != data )
	{
		p = x;
		if (data > x->data)
		{	
			x = x->rightChild;
		}
		else
		{
			x = x->leftChild;
		}
	}

	if (x == nullptr)
	{	//當搜尋不到要查詢的節點時,返回應出現位置的父節點的指標
		return p;
	}
	else
	{	//當搜尋到要查詢的節點時,返回該節點的指標
		return x;
	}
}

//函式功能:3+4重構實現旋轉操作(Private)
//函式引數:三個節點和四棵子樹的二級指標
//函式返回值:void
template <class ElemType>
void AVL<ElemType>::Connect34(BinTreeNode<ElemType> *a, BinTreeNode<ElemType> *b, \
								BinTreeNode<ElemType> *c, \
								BinTreeNode<ElemType> *T0, BinTreeNode<ElemType> *T1, \
								BinTreeNode<ElemType> *T2, BinTreeNode<ElemType> *T3)
{	//a,c為b的左右孩子,T0,T1,T2,T3又分別為a,c的左右子樹
	b->leftChild = a;
	a->parent = b;
	b->rightChild = c;
	c->parent = b;

	a->leftChild = T0;
	if (T0)	T0->parent = a;	//T0可能為空
	a->rightChild = T1;
	if (T1)	T1->parent = a;	//T1可能為空

	c->leftChild = T2;
	if (T2) T2->parent = c;	//T2可能為空
	c->rightChild = T3;
	if (T3) T3->parent = c;	//T3可能為空

	//更新三個節點的樹高
	UpdateHeight(*a);
	UpdateHeight(*c);
	UpdateHeight(*b);
}

//函式功能:計算節點的失衡值(Private)
//函式引數:要計算的節點的引用
//函式返回值:計算得到的失衡值
template <class ElemType>
int AVL<ElemType>::CalculateBF(BinTreeNode<ElemType> &node)
{	//失衡值計算方法為左子樹高減去右子樹高
	int leftTreeHeight = (node.leftChild == nullptr ? 0 : node.leftChild->height);	//左子樹的高
	int rightTreeHeight = (node.rightChild == nullptr ? 0 : node.rightChild->height);	//右子樹的高

	return leftTreeHeight - rightTreeHeight;
}

//函式功能:進行旋轉操作(Private)
//函式引數:失衡節點引用
//函式返回值:void
template <class ElemType>
void AVL<ElemType>::Rotate(BinTreeNode<ElemType> &unbalancedNode)
{
	int bf = CalculateBF(unbalancedNode);	//計算出失衡節點的平衡值

	if (bf > 0)
	{
		if (CalculateBF(*unbalancedNode.leftChild) >= 0)
		{	//zig型
			BinTreeNode<ElemType> *x = unbalancedNode.leftChild->leftChild;	//失衡節點的孫節點
			BinTreeNode<ElemType> *p = unbalancedNode.leftChild;	//失衡節點的子節點
			BinTreeNode<ElemType> *g = &unbalancedNode;	//失衡節點
			//p頂替g的位置
			p->parent = g->parent;
			if (g->parent != nullptr)
			{	//失衡節點有父節點時
				if (g->parent->leftChild == g)	g->parent->leftChild = p;
				else g->parent->rightChild = p;
			}
			else
			{	//失衡節點無父節點時
				this->root = p;
			}
			
			Connect34(x, p, g, x->leftChild, x->rightChild, p->rightChild, g->rightChild);
		}
		else
		{	//zag-zig型
			BinTreeNode<ElemType> *x = unbalancedNode.leftChild->rightChild;	//失衡節點的孫節點
			BinTreeNode<ElemType> *p = unbalancedNode.leftChild;	//失衡節點的子節點
			BinTreeNode<ElemType> *g = &unbalancedNode;	//失衡節點
			//x頂替g的位置
			x->parent = g->parent;
			if (g->parent != nullptr)
			{	//失衡節點有父節點時
				if (g->parent->leftChild == g)	g->parent->leftChild = x;
				else g->parent->rightChild = x;
			}
			else
			{	//失衡節點無父節點時
				this->root = x;
			}

			Connect34(p, x, g, p->leftChild, x->leftChild, x->rightChild, g->rightChild);
		}
	}
	else if (bf < 0)
	{
		if (CalculateBF(*unbalancedNode.rightChild) <= 0)
		{	//zag型
			BinTreeNode<ElemType> *x = unbalancedNode.rightChild->rightChild;	//失衡節點的孫節點
			BinTreeNode<ElemType> *p = unbalancedNode.rightChild;	//失衡節點的子節點
			BinTreeNode<ElemType> *g = &unbalancedNode;	//失衡節點
			//p頂替g的位置
			p->parent = g->parent;
			if (g->parent != nullptr)
			{	//失衡節點有父節點時
				if (g->parent->leftChild == g)	g->parent->leftChild = p;
				else g->parent->rightChild = p;
			}
			else
			{	//失衡節點無父節點時
				this->root = p;
			}

			Connect34(g, p, x, g->leftChild, p->leftChild, x->leftChild, x->rightChild);
		}
		else
		{	//zig-zag型
			BinTreeNode<ElemType> *x = unbalancedNode.rightChild->leftChild;	//失衡節點的孫節點
			BinTreeNode<ElemType> *p = unbalancedNode.rightChild;	//失衡節點的子節點
			BinTreeNode<ElemType> *g = &unbalancedNode;	//失衡節點
			//x頂替g的位置
			x->parent = g->parent;
			if (g->parent != nullptr)
			{	//失衡節點有父節點時
				if (g->parent->leftChild == g)	g->parent->leftChild = x;
				else g->parent->rightChild = x;
			}
			else
			{	//失衡節點無父節點時
				this->root = x;
			}

			Connect34(g, x, p, g->leftChild, x->leftChild, x->rightChild, p->rightChild);
		}
	}
}

//函式功能:插入操作(供使用者呼叫)
//函式引數:要插入的節點的資料
//函式返回值:bool型別,返回OK or FALSE
template <class ElemType>
bool AVL<ElemType>::Insert(ElemType data)
{
	BinTreeNode<ElemType> *posi = Search(data);	//記錄待插入節點的位置
	if (posi != nullptr && posi->data == data)
	{	//當要插入的資料已經存在時
		return FALSE;
	}

	BinTreeNode<ElemType> *node;	
	node = new BinTreeNode<ElemType>;
	
	//將待插入的資料包裝成節點
	node->data = data;
	node->height = 1;
	node->leftChild = node->rightChild = nullptr;
	node->parent = posi;
	if (posi != nullptr)
	{	//當樹不為空時
		(data < posi->data ? posi->leftChild : posi->rightChild) = node;
	}
	else
	{	//當樹還為空時
		this->root = node;
	}
	
	UpdateHeight(*node);
	
	BinTreeNode<ElemType> *g;	//g為插入節點的爺節點
	g = node->parent;

	if (g != nullptr)
	{
		while (g != nullptr&&abs(CalculateBF(*g)) <= 1)
		{	//向上查詢失衡節點
			g = g->parent;
		}
		
		if (g != nullptr)
		{	//當存在失衡節點時,進行旋轉操作
			Rotate(*g);	//若爺節點的失衡值的絕對值大於1,進行旋轉操作
		}
	}
	return OK;
}

//函式功能:刪除節點操作(供使用者呼叫)
//函式引數:待刪除的資料
//函式返回值:bool型別,OK or FALSE
template <class ElemType>
bool AVL<ElemType>::Delete(ElemType data)
{
	BinTreeNode<ElemType> *posi = this->Search(data);	//查詢待刪除的節點

	if (posi == nullptr || posi->data != data)
	{	//當找不到要刪除的節點時,返回FALSE
		return FALSE;
	}
	
	BinTreeNode<ElemType> *succ;	//待刪除節點的接替節點,這裡用它的前驅結點
	BinTreeNode<ElemType> *unbalancedCheckNode;	//失衡檢查節點	

	if (posi->leftChild == nullptr)
	{	//當刪除節點的左孩子為空時
		succ = posi->rightChild;
		if (posi->parent == nullptr)
		{	//當要刪除的節點即為根節點時
			this->root = succ;	//將樹的根節點替換成要刪除節點的接替節點
			if (succ != nullptr)	succ->parent = nullptr;	//修改接替節點的父節點
		}
		else
		{	//當要刪除的節點不為根節點時
			(posi->parent->leftChild == posi ? posi->parent->leftChild : posi->parent->rightChild) = succ;	 //posi的父節點的孩子替換為succ
			if (succ != nullptr)	succ->parent = posi->parent;	//修改接替節點的父節點
		}		
		unbalancedCheckNode = posi->parent;	//向上檢查失衡
		delete posi;	//刪除節點
		posi = nullptr;	//將節點置空
	}
	else if (posi->leftChild->rightChild == nullptr)
	{	//當刪除節點的左孩子的右孩子為空時
		succ = posi->leftChild;
		if (posi->parent == nullptr)
		{	//當要刪除的節點即為根節點時
			this->root = succ;	//將樹的根節點替換成要刪除節點的接替節點
			succ->parent = nullptr;	//修改接替節點的父節點
			succ->rightChild = posi->rightChild;	//接替節點的右孩子變為刪除節點的右孩子
			if (posi->rightChild != nullptr)	posi->rightChild->parent = succ;
		}
		else
		{	//當要刪除的節點不為根節點時
			(posi->parent->leftChild == posi ? posi->parent->leftChild : posi->parent->rightChild) = succ;	 //posi的父節點的孩子替換為succ
			succ->parent = posi->parent;
			succ->rightChild = posi->rightChild;	//接替節點的右孩子變為刪除節點的右孩子
			if (posi->rightChild != nullptr)	posi->rightChild->parent = succ;
		}
		unbalancedCheckNode = succ;	//向上檢查失衡
		delete posi;	//刪除節點
		posi = nullptr;	//將節點置空
	}
	else
	{	//當刪除結點的左孩子不為空且左孩子的右孩子不為空
		succ = posi->leftChild;
		while (succ->rightChild != nullptr)
		{	//尋找刪除節點的前驅結點
			succ = succ->rightChild;
		}

		posi->data = succ->data;	//將刪除結點的資料用接替結點的資料代替
		unbalancedCheckNode = succ->parent;
		succ->parent->rightChild = succ->leftChild;	//接替節點的左孩子替代接替節點的父節點的右孩子
		if (succ->leftChild != nullptr)	succ->leftChild->parent = succ->parent;	//更新接替節點左孩子的父節點
		delete succ;	//刪除結點
		succ = nullptr;	//將指標置空
	}

	UpdateHeight(*unbalancedCheckNode);	//更新樹高
	BinTreeNode<ElemType> *ancestorNode = unbalancedCheckNode;	//刪除節點的祖先結點
	while (ancestorNode != nullptr)
	{	//由於刪除操作可能回引起失衡傳播,所以要一直向上檢查是否失衡
		if (abs(CalculateBF(*ancestorNode))>1)
		{	//當失衡值的絕對值大於1時進行旋轉操作
			Rotate(*ancestorNode);
		}
		ancestorNode = ancestorNode->parent;
	}

	return OK;
}

//函式功能:將平衡二叉樹的節點資料打印出來(供使用者呼叫)
//函式引數:無
//函式返回值:void
template <class ElemType>
void AVL<ElemType>::Print()
{
	PrintOut(this->root);
}

//函式功能:將平衡二叉樹的節點資料打印出來(Private)
//函式引數:開始列印的節點的指標
//函式返回值:void
template <class ElemType>
void AVL<ElemType>::PrintOut(BinTreeNode<ElemType> *beginNode)
{	//採用中序遍歷
	if (beginNode != nullptr)
	{
		this->PrintOut(beginNode->leftChild);
		std::cout << beginNode->data << "\t";
		this->PrintOut(beginNode->rightChild);
	}
}

以上為平衡二叉樹類的模板(繼承二叉樹類)

    我想說的,基本上程式碼中都註釋了。為了程式的安全性考慮,我也儘可能採用引用代替指標以及把一些類函式定義為私有函式,避免被使用者濫用。但有同學可能會吐槽這程式碼怎麼會這麼長,我只想說,詳細註釋是有代價的,當然,程式碼片段中存在部分重複,這是未優化的結果,如果同學們有什麼好的改進意見可以在評論區留言給我。上述對二叉樹類的定義也過分簡單,這是因為這個二叉樹類模板我是第一次創建出來的,以後會慢慢會豐富該類模板的介面,不過在這個程式中,這個簡單的不能再簡單的二叉樹類是完全足夠的。除此之外,我還是要吐槽這個程式碼中模板用的形如虛設,因為未對比較操作符(<>=)進行過載,這就導致了比較的範圍十分有限,如果再過載比較操作符的話,我們就能比較筆和橡皮的大小了,同學們可以一試。

 接下來未測試主函式中的內容:

//平衡二叉樹測試函式
//SJ2050

#include <iostream>
#include "AVL.h"

using namespace std;

int main()
{
	AVL<int> *T = new AVL<int>;	//建立一棵平衡二叉樹

	int a[10] = { 3,2,1,4,5,6,7,10,9,8 };
	for (int i = 0; i < 10; i++)
	{
		T->Insert(a[i]);
	}
	T->Print();
	cout << endl;	//換行
	T->Delete(4);
	T->Print();
	cout << endl;	//換行
	T->Delete(2);
	T->Print();
	cout << endl;	//換行
	T->Delete(7);
	T->Print();
	cout << endl;	//換行
	T->Delete(8);
	T->Print();
	cout << endl;	//換行
	T->Insert(11);
	T->Print();
	cout << endl;	//換行

	delete T;
	system("pause");
	return 0;
}

輸出結果為:

執行效果

雖然該程式經過我多次除錯,但還是可能存在bug,如果你發現的話,請在評論區告訴我,我會馬上修改,謝謝閱讀!

謝謝閱讀