3. 二叉平衡树

一、平衡二叉树是带有平衡条件的二叉查找树

平衡条件:平衡二叉树的每个结点的左子树和右子树的高度最多差1。

平衡因子 bf :左子树的高度减去右子树的高度,显然 bf 的取值范围是 [ -1, 1 ] 。每一个结点(在其结点结构中)保留平衡因子 bf 。 

/* 平衡二叉树的结点结构 */ 
struct BinaryTreeNode {
	int bf;					// 平衡因子,当前结点的左右子树的高度差 
	int value;
	BinaryTreeNode *lChild;
	BinaryTreeNode *rChild;
}; 

补:虽然平衡二叉树能确保树的高度为O(logn),但同时我们对其的插入/删除操作都需保持它的平衡条件

由于平衡二叉树的平衡条件比较严格,故其维护的代价也较大,一般只适用于查找次数多、插入和删除次数少的情况。

从实际出发,平衡条件较为宽松的红黑树是更好的选择,因为对其进行插入或删除操作付出的代价相对较小。

当然,如果在应用场景中插入和删除操作并不频繁,只是对查找要求较高,那么平衡二叉树还是较优于红黑树。

 

二、旋转操作——保持平衡二叉树的平衡条件

1. 失衡的四种情况(把当前因失衡而必须重新平衡的结点叫做α)

  • 对α的左儿子的左子树进行一次插入(左-左情况,以α为轴右旋)
  • 对α的左儿子的右子树进行一次插入(左-右情况,先左旋,再右旋)
  • 对α的右儿子的左子树进行一次插入(右-左情况,先右旋,再左旋)
  • 对α的右儿子的右子树进行一次插入(右-右情况,以α为轴左旋)

        

                 

绿色的结点为新加入的结点。虚线连接的三个结点是涉及旋转的结点。虚线的方向代表相应的失衡情况。

 

2. 示例

【左-右】(涉及的双旋结点为8、9、10)

(1)以8为轴进行左旋(逆时针旋转)

(2)以10为轴进行右旋(顺时针旋转)

(3)得到平衡二叉树

分析:当有多个结点的 | bf | > 1时,需要重新平衡的结点 α 是离新加入结点最近的。

 

【右-左】(涉及的双旋结点为7、15、16)

(1)以16为轴进行右旋(顺时针旋转)

(2)以7为轴进行左旋(逆时针旋转)

(3)得到平衡二叉树

 

【右-左】涉及的双旋结点为6、7、15

(1)以15为轴进行右旋(顺时针旋转)

(2)以6为轴进行左旋(逆时针旋转)

(3)得到平衡二叉树

 

【小结:如何判断是哪种失衡情况】

结点α:离新加入结点最近的且| bf | > 1 的结点 。

以上图为例,结点α为6,然后再观察从结点α到新加入的结点14形成的路径“6-->15-->7-->14”,发现“6-->15-->7”形成的正是右-左这一情况。

 

3. 代码

// 左旋:借助图来写代码 
void LRotate(BinaryTreeNode **pRoot)
{
	BinaryTreeNode *pNewRoot = (*pRoot)->rChild;	// 新的根结点是当前根结点的右子树 
	(*pRoot)->rChild = pNewRoot->lChild;			// 新的根结点的左子树应挂载到根结点的右子树 
	pNewRoot->lChild = *pRoot;						// 新的根结点的左子树是当前根结点 
	*pRoot = pNewRoot;								// 修改当前根结点的指向 
} 

// 右旋:借助图来写代码
void RRotate(BinaryTreNode **pRoot)
{
	BinaryTreeNode *pNewRoot = (*pRoot)->lChild;
	(*pRoot)->lChild = pNewRoot->rChild;
	pNewRoot->rChild = *pRoot;
	*pRoot = pNewRoot;
} 

/* 左平衡:pRoot的左子树高于右子树,需平衡左子树 */
/* 先检查失衡情况是左-左还是左-右 
 * 左-左情况:(*pRoot)->lChild->bf的值为1(即LH) 
 * 左-右情况:(*pRoot)->lChild->bf的值为-1(即RH) 
 */ 
void LeftBalance(BinaryTreeNode **pRoot)
{
	BinaryTreeNode *lChild, *lChild_rChild;	// 左孩子,左孩子的右孩子 
	lChild = (*pRoot)->lChild; 
	lChild_rChild = lChild->rChild;
	
	switch(lChild->bf) {
		// 左-左情况 
		case LH:
			lChild->bf = (*pRoot)->bf = EH;	// 作用和RH情况下的switch语句 
			RRotate(pRoot);					// 以pRoot为轴左旋  
			break;
		// 左-右情况	
		case RH:
			// 该switch语句的作用是更新各个结点的bf值,它们对应调整后的值 
			switch(lChild_rChild->bf) {
				case LH:
					(*pRoot)->bf = RH;
					lChild->bf = EH;
					break;
				case RH:
					(*pRoot)->bf = EH;
					lChild->pf = LH;
					break;
				case EH:
					(*pRoot)->bf = EH;
					lChild->pf = EH;
				default:
					break;
			}
			lChild_rChild->bf = EH;			// 调整后的新的根结点为lChild_rChild 
			LRotate(&lChild);				// 以lChild为轴左旋 
			RRotate(pRoot);					// 以pRoot为轴右旋 
			break;
	}
} 



/* 右平衡:pRoot的右子树高于左子树,需平衡右子树 */
/* 先检查失衡情况是右-右还是右-左 
 * 右-右情况:(*pRoot)->rChild->bf的值为-1(即RH) 
 * 右-左情况:(*pRoot)->rChild->bf的值为1(即lH) 
 */ 
void RightBalance(BinaryTreeNode **pRoot) 
{
	BinaryTreeNode *rChild, *rChild_lChild;	// 右孩子,右孩子的左孩子 
	rChild = (*pRoot)->rChild;
	rChild_lChild = rChild->lChild;
	
	switch(rChild->bf) {
		case RH:
			rChild->bf = (*pRoot)->bf = EH;
			LRotate(pRoot);
			break;
		case LH:
			switch(rChild_lChild->bf) {
				case LH:
					(*pRoot)->bf = EH;
					rChild->bf = RH;
					break;
				case RH:
					(*pRoot)->bf = LH;
					rChild->bf = EH;
					break;
				case EH:
					(*pRoot)->bf = EH;
					rChild->bf = EH;
					break; 
				default:
					break;
			}
			rChild_lChild->bf = EH;
			RRotate(&rChild);
			LRotate(pRoot);
			break;
	}
}

  

三、插入操作

/* 往平衡二叉树上插入结点 */
bool insert(BinaryTreeNode **pRoot, int x, bool *isTaller)
{
	// 找到插入位置(树中没有值等于x的结点) 
	if(*pRoot == nullptr) {
		*pRoot = new BinaryTreeNode;
		(*pRoot)->bf = EH;
		(*pRoot)->value = x;
		(*pRoot)->lChild = nullptr;
		(*pRoot)->rChild = nullptr;
		*isTaller = true;
		return true; 
	}
	else {
		// 树中有值为x的结点,不用插入了 
		if((*pRoot)->value == x) {
			*taller = false;
			return false;
		}
		// 往左子树中插入 
		if((*pRoot->value) > x) {
			// 树中有相同的结点,直接返回 
			if(!insert(&((*pRoot)->lChild), x, isTaller)) {
				*isTaller = false;
				return false;
			}
			// isTaller为真,则意味着树长高了,并且往左子树中插入了结点 
			if(*isTaller) {
				// 检查平衡因子bf,根据相应的情况做出对应的修改和旋转 
				switch((*pRoot)->bf) {
					// LH表示在插入该结点之前,左子树就比右子树高1 
					// 故插入该结点后,左子树将比右子树高2 
					case LH: 
						LeftBalance(pRoot);		// 使左子树平衡 
						*isTaller = false; 		// 已平衡,令isTaller为假,往上递归就不再进入此语句了 
						break;
					// 在插入该结点之前,右子树比左子树高1 
					case RH:
						(*pRoot)->bf = EH;
						*isTaller = false;
						break;
					// 在插入该结点之前,左子树和右子树一样高 
					// 故插入该结点后,左子树将比右子树高1 
					case EH:
						(*pRoot)->bf = LH;
						*isTaller = true;		// 继续往上递归 
						break;
				}
			}
		}
		// 往右子树中插入 
		else {
			if(!insert(&((*pRoot)->rChild), x, isTaller)) {
				*isTaller = false;
				return false;
			}
			// isTaller为真,则意味着往右子树中插入了结点
			if(*isTaller) {
				switch((*pRoot)->bf) {
					case LH:
						(*pRoot)->bf = EH;
						*isTaller = false;
						break;
					case RH:
						RightBalance(pRoot);
						*isTaller = false;
						break;
					case EH:
						(*pRoot)->bf = RH;
						*isTaller = true;
						break;
				}
			}			
		}
	}
	
}

  

 四、完整代码

#include <iostream>
#include <stack>
#include <queue>

using namespace std;

#define	EH	0		// 等高 
#define LH	1		// 左高 
#define	RH	-1		// 右高 


/* 二叉平衡树的结点结构 */ 
struct BinaryTreeNode {
	int bf;					// 平衡因子,当前结点的左右子树的高度差 
	int value;
	BinaryTreeNode *lChild;
	BinaryTreeNode *rChild;
}; 

// 左旋:借助图来写代码 
void LRotate(BinaryTreeNode **pRoot)
{
	BinaryTreeNode *pNewRoot = (*pRoot)->rChild;	// 新的根结点是当前根结点的右子树 
	(*pRoot)->rChild = pNewRoot->lChild;			// 新的根结点的左子树应挂载到根结点的右子树 
	pNewRoot->lChild = *pRoot;						// 新的根结点的左子树是当前根结点 
	*pRoot = pNewRoot;								// 修改当前根结点的指向 
} 

// 右旋:借助图来写代码
void RRotate(BinaryTreeNode **pRoot)
{
	BinaryTreeNode *pNewRoot = (*pRoot)->lChild;
	(*pRoot)->lChild = pNewRoot->rChild;
	pNewRoot->rChild = *pRoot;
	*pRoot = pNewRoot;
} 

/* 左平衡:pRoot的左子树高于右子树,需平衡左子树 */
/* 先检查失衡情况是左-左还是左-右 
 * 左-左情况:(*pRoot)->lChild->bf的值为1(即LH) 
 * 左-右情况:(*pRoot)->lChild->bf的值为-1(即RH) 
 */ 
void LeftBalance(BinaryTreeNode **pRoot)
{
	BinaryTreeNode *lChild, *lChild_rChild;	// 左孩子,左孩子的右孩子 
	lChild = (*pRoot)->lChild; 
	lChild_rChild = lChild->rChild;
	
	switch(lChild->bf) {
		// 左-左情况 
		case LH:
			lChild->bf = (*pRoot)->bf = EH;	// 作用和RH情况下的switch语句 
			RRotate(pRoot);					// 以pRoot为轴左旋  
			break;
		// 左-右情况	
		case RH:
			// 该switch语句的作用是更新各个结点的bf值,它们对应调整后的值 
			switch(lChild_rChild->bf) {
				case LH:
					(*pRoot)->bf = RH;
					lChild->bf = EH;
					break;
				case RH:
					(*pRoot)->bf = EH;
					lChild->bf = LH;
					break;
				case EH:
					(*pRoot)->bf = EH;
					lChild->bf = EH;
				default:
					break;
			}
			lChild_rChild->bf = EH;			// 调整后的新的根结点为lChild_rChild 
			LRotate(&lChild);				// 以lChild为轴左旋 
			RRotate(pRoot);					// 以pRoot为轴右旋 
			break;
	}
} 



/* 右平衡:pRoot的右子树高于左子树,需平衡右子树 */
/* 先检查失衡情况是右-右还是右-左 
 * 右-右情况:(*pRoot)->rChild->bf的值为-1(即RH) 
 * 右-左情况:(*pRoot)->rChild->bf的值为1(即lH) 
 */ 
void RightBalance(BinaryTreeNode **pRoot) 
{
	BinaryTreeNode *rChild, *rChild_lChild;	// 右孩子,右孩子的左孩子 
	rChild = (*pRoot)->rChild;
	rChild_lChild = rChild->lChild;
	
	switch(rChild->bf) {
		case RH:
			rChild->bf = (*pRoot)->bf = EH;
			LRotate(pRoot);
			break;
		case LH:
			switch(rChild_lChild->bf) {
				case LH:
					(*pRoot)->bf = EH;
					rChild->bf = RH;
					break;
				case RH:
					(*pRoot)->bf = LH;
					rChild->bf = EH;
					break;
				case EH:
					(*pRoot)->bf = EH;
					rChild->bf = EH;
					break; 
				default:
					break;
			}
			rChild_lChild->bf = EH;
			RRotate(&rChild);
			LRotate(pRoot);
			break;
	}
}


/* 往平衡二叉树上插入结点 */
bool insert(BinaryTreeNode **pRoot, int x, bool *isTaller)
{
	// 找到插入位置(树中没有值等于x的结点) 
	if(*pRoot == nullptr) {
		*pRoot = new BinaryTreeNode;
		(*pRoot)->bf = EH;
		(*pRoot)->value = x;
		(*pRoot)->lChild = nullptr;
		(*pRoot)->rChild = nullptr;
		*isTaller = true;
		return true; 
	}
	else {
		// 树中有值为x的结点,不用插入了 
		if((*pRoot)->value == x) {
			*isTaller = false;
			return false;
		}
		// 往左子树中插入 
		if((*pRoot)->value > x) {
			// 树中有相同的结点,直接返回 
			if(!insert(&((*pRoot)->lChild), x, isTaller)) {
				*isTaller = false;
				return false;
			}
			// isTaller为真,则意味着树长高了,并且往左子树中插入了结点 
			if(*isTaller) {
				// 检查平衡因子bf,根据相应的情况做出对应的修改和旋转 
				switch((*pRoot)->bf) {
					// LH表示在插入该结点之前,左子树就比右子树高1 
					// 故插入该结点后,左子树将比右子树高2 
					case LH: 
						LeftBalance(pRoot);		// 使左子树平衡 
						*isTaller = false; 		// 已平衡,令isTaller为假,往上递归就不再进入此语句了 
						break;
					// 在插入该结点之前,右子树比左子树高1 
					case RH:
						(*pRoot)->bf = EH;
						*isTaller = false;
						break;
					// 在插入该结点之前,左子树和右子树一样高 
					// 故插入该结点后,左子树将比右子树高1 
					case EH:
						(*pRoot)->bf = LH;
						*isTaller = true;		// 继续往上递归 
						break;
				}
			}
		}
		// 往右子树中插入 
		else {
			if(!insert(&((*pRoot)->rChild), x, isTaller)) {
				*isTaller = false;
				return false;
			}
			// isTaller为真,则意味着往右子树中插入了结点
			if(*isTaller) {
				switch((*pRoot)->bf) {
					case LH:
						(*pRoot)->bf = EH;
						*isTaller = false;
						break;
					case RH:
						RightBalance(pRoot);
						*isTaller = false;
						break;
					case EH:
						(*pRoot)->bf = RH;
						*isTaller = true;
						break;
				}
			}			
		}
	}
	
}

/* 中序遍历 */ 
void InOrderTraverse(BinaryTreeNode *pRoot)
{
	if(pRoot == nullptr)
		return;
		
	InOrderTraverse(pRoot->lChild);
	cout << pRoot->value;
	InOrderTraverse(pRoot->rChild);
}

/* 求树的高度 */
int GetHeight(BinaryTreeNode *pRoot)
{
	if(pRoot == nullptr)	return 0;

	int leftHeight = GetHeight(pRoot->lChild);
	int rightHeight = GetHeight(pRoot->rChild);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
} 

int main()
{
	int arr[7] = {4, 3, 2, 7, 6, 8, 5};
	BinaryTreeNode *pTree = nullptr;
	bool isTaller = false;
	
	for (int i = 0; i < 7; ++i) {
		insert(&pTree, arr[i], &isTaller);
	}
	
	cout << "提示:平衡二叉树创建完毕!" << endl;

	cout << "提示:树的高度为" << GetHeight(pTree) << endl; 
	cout << "提示:中序遍历平衡二叉树!" << endl;
	InOrderTraverse(pTree);
	cout << endl;
	return 0;
}

测试结果:

上段代码建立的平衡二叉树的最理想高度应为3,但由于平衡二叉树的平衡条件是允许左右子树存在高度差的,故代码运行结果为4。下图为理想情况下应建立的二叉树。

  

 

 

 

 

  

 

posted @ 2018-09-01 20:38  GGBeng  阅读(658)  评论(0编辑  收藏  举报