算法导论 13-3 AVL平衡二叉树

题目

   《算法导论 13-3 AVL平衡二叉树》

AVL树的主要操作

   在本篇博客中我们只实现AVL平衡树的insert、erase、edit和locate以及两个遍历操作,当然还包括一些配套函数,主要论述insert和erase操作,其他诸如successor、minimum等等操作可以参考二叉查找树红黑树,实现很类似,并没有太大不同。

   此外,这些操作我将全部用递归实现,所以和上述两种树以及treap(树堆)节点有所不同,不再引入父指针,因而各种旋转操作也会有所不同,不过原理是一样的。


单旋转和双旋转

   假设当前需要平衡调整的子树根为r,由于不平衡,则说明其左右子树高度差为2,。空树高度为-1.有以下四种情形:

   1、对r的左孩子的左子树进行了一次插入;

   2、对r的左孩子的右子树进行了一次插入;

   3、对r的右孩子的右子树进行了一次插入;

   4、对r的右孩子的左子树进行了一次插入;

可以看出:情况1和3对称,2和4对称。


左旋和右旋

《算法导论 13-3 AVL平衡二叉树》


情况1,左左插入

《算法导论 13-3 AVL平衡二叉树》

   在上图中插入关键字6之后,递归插入过程将结束,然后逐层返回,同时判断是否该调整。在返回到根节点为8的子树时发现树不平衡,左子树比右子树高2,那么需要调整,由于是在8的左孩子的左子树插入的,则只需将8绕着7来一次右旋(rightRotate)即可恢复平衡,然后返回新子树根,如右图。

   可以看出:调整时从最深的不平衡节点开始的。


情况2,右右插入

《算法导论 13-3 AVL平衡二叉树》

    在上图中插入关键字5之后,递归插入过程结束,逐层返回,在3处第一次发现不平衡,由于是在3的右孩子的右子树插入,则只需将3绕着4来一次左旋(leftRotate)转即可恢复平衡,然后返回新子树根,如右图。


情况3,右左插入

《算法导论 13-3 AVL平衡二叉树》

    在上图中插入关键字15后,递归插入过程将结束,逐层返回新根,在7处首次发现不平衡,由于是在7的右孩子的左子树插入的,那么可以先将7的右孩子关键字16绕着15右旋,然后在将关键字7绕着它的新右孩子左旋(rightLeftRotate)即可恢复平衡,然后返回新子树根,如右图。


情况4,左右插入

《算法导论 13-3 AVL平衡二叉树》

    在上图中插入关键字D,递归过程将结束,然后逐层返回新根,在根T处第一次发现不平衡,由于是在T的左孩子的右子树插入的,则只需先将T的左孩子A绕着它的右孩子B左旋,如中图,在将T绕着它的新的左孩子B右旋(leftRightRotate)即可恢复平衡,然后返回新子树根,如右图。


有了上述对左旋右旋,以及四种情况下的调整策略,不难得出递归的插入程序。

insert_aux(node *r,const Key &k,const Value &v)

    插入操作主要是由这个辅助函数递归实现,r是当前子树根。流程如下: 

1、若r为空,则说明已经到达叶子,则在此处构造节点,并返回;

2、若r的关键字较大,则向左递归插入节点,若较小则转4;结束后,返回到此处,若需调整则转3,否则继续返回;

3、显然,左子树较高,若是左左插入则调用rightRotate调整;否则为左右插入,则调用leftRightRotate调整。

4、r的关键字较小,须向右递归插入;结束后,返回此处,若需调整则转5,否则继续返回;

5、显然,右子树高,若是右右插入则调用leftRotate调整;否则为右做插入,则调用rightLeftRotate调整。

    在这个递归插入过程中有一点需要注意,当递归到叶子成功插入节点后,将逐层返回(其实也就是沿着查找路径逆向返回),在每次返回时都要返回该子树的根,以供上一层更新(左孩子亦或是右孩子),当然,对于最后一次返回将更新树根。返回时,如果该子树经历过调整,那返回的必然是新的子根,那么上一层节点的左/右孩子将会改变;如果没有调整,则返回之前的。这样可以保证整棵树都会得到维护。


erase_aux(node *r,const Key &k)  

   查找树的删除我们通常都是采用替代法,即如果被删节点有两个孩子,则删除其后继(也就是其右子树的最小关键字节点),若最多只有一个孩子,则删除它本身。在这里我们也采用这种方法,但是也有所不同,我们将用递归,这与通常情况不太一样(参考上面关于三种另外查找树的链接),主要还是因为没有引入父指针的原因,当然,本篇博客开头即说过所有函数皆用递归实现。

   流程如下:

1、若r为空,则说明没有该关键字,直接返回;

2、否则,若k小于该节点关键字,则向左递归删除;结束后返回到此处,若需调整则转3,否则直接转4;

3、显然,r的右子树比左子树高2,则相当于右子树插入了一个节点,情况和插入类似,不过也有所不同,即若r右子树有右孩子则只需将r绕着其右孩子做一次左旋(leftRotate)即可恢复平衡;若没有右孩子,则必然存在左孩子,则调用rightLeftRotate即可恢复平衡,返回;

4、若k大于r关键字,则向右递归删除;结束后返回到此处,若需调整则转5,否则直接转6;

5、和3对称,r的左子树比右子树高2,相当于在左子树插入一个节点,那么若r的左孩子存在左孩子,则调用rightRotate即可,若不存在左孩子,则必然存在右孩子,那么调用leftRightRotate即可恢复平衡,返回;

6、到了这一步说明r正是需要删除的节点,若r最多有一个孩子,则转7,否则转8;

7、那么需要删除r本身。先记下r的非空孩子,然后释放r的空间,返回新根,即r的非空孩子;

8、调用过程erase_successor(node*,node*&),删除其后继,把其内容复制到r中,释放期内存,返回根r。


erase_successor(node *r,node *&del)

    该函数摘掉r子树的最小值节点存入del,如若需要则调用旋转过程使树平衡。同样地,采用递归实现。流程如下:

1、若r不存在左孩子了,则说明r本身即为该子树最小值节点,那么将r摘下,存入del,返回其右孩子(可能为空);

2、若存在,则递归向左寻找,结束时赶回此处;如若需要则进行调整,和删除算法流程的步骤3一样,返回。

    删除过程和插入过程一样,递归结束时将逐层返回,需要注意每次要返回当前子树的新根,以供上层更新,从而维护整棵树。


到此,最繁琐复杂的两个算法插入和删除就介绍完毕了,后面将会给出源代码,注释很详细,结合着看效果更佳。其他几个算法就不介绍了,比较简单,看看源代码即可。


习题

(a)

  《算法导论 13-3 AVL平衡二叉树》

AVL树的平衡条件比红黑树严格不少,对于高h的AVL树,其节点的最小数目满足上面的公式,可以证明若树节点数目为n,那么树高最多为1.44lg(n+2)-1.328.


(b)

balance(r)
{
	if (getHeight(r->left) - getHeight(r->right) == 2)
	{//若插入后,左子树过高,失去平衡
		if (compare(k, r->left->key))//1、在当前树的左子树的左边插入
			r = rightRotate(r, r->left);//则一次右旋即可,并更新新根
		else r = leftRightRotate(r, r->left);//2、在当前树的左子树的右边插入,则先左旋,再右旋
	}
	else if (getHeight(r->right) - getHeight(r->left) == 2)
	{//若右子树过高
		if (compare(r->right->key, k))//3、在右子树的右边插入
			r = leftRotate(r, r->right);
		else r = rightLeftRotate(r, r->right);//4、在右子树的左边插入
	}
	return r;
}

(c) 见代码,注释详细。

//AVL平衡二叉树

#include<iostream>

using namespace std;

#define MAX(i,j) (i > j ? i : j)

template <typename Key,typename Value>
struct node
{//树节点
	Key key;
	Value value;
	int height = 0;
	node *left = nullptr;
	node *right = nullptr;
	node(const Key &k, const Value &v) :key(k), value(v){}
	void print()const
	{
		cout << "key: " << key << "\t\t" << "value: " << value << '\t' << "height: " << height << endl;
	}
};

template <typename Key,typename Value,class CompareKey = less<Key>>
class AVLTree
{//AVL树
private:
	typedef node<Key, Value>				node;
	node *root;
	CompareKey compare;//比较器
private:
	AVLTree(node *r, const CompareKey &c) :root(r), compare(c){}//该构造函数只供内部使用
	node* insert_aux(node*, const Key&, const Value&);//插入辅助
	node* erase_aux(node*, const Key&);
	node* erase_successor(node*,node*&);//当删除节点有两个孩子时,将删除其后继代替
	node* locate_aux(node*, const Key&)const;
	node* leftRotate(node*, node*);//左旋
	node* rightRotate(node*, node*);
	node* leftRightRotate(node*, node*);//先左旋,再右旋
	node* rightLeftRotate(node*, node*);
	void destroy_aux(node*);
	int getHeight(node *p)const
	{//获得当前节点的高度
		if (p == nullptr) return -1;
		else return p->height;
	}
public:
	AVLTree() :root(nullptr), compare(){}
	AVLTree(const CompareKey &c) :root(nullptr), compare(c){}
	bool empty()const
	{
		return root == nullptr; 
	}
	void insert(const Key &k, const Value &v)
	{
		root = insert_aux(root, k, v);
	}
	void erase(const Key &k)
	{
		erase_aux(root, k);
	}
	bool locate(const Key &k)const
	{
		return (locate_aux(root, k) != nullptr);
	}
	bool edit(const Key &k, const Value &new_value)
	{
		node *p = locate_aux(root,k);
		if (p != nullptr) p->value = new_value;
		return (p != nullptr);
	}
	void clear()
	{//清空树
		destroy_aux(root);
		root = nullptr;
	}
	void inTraversal()const;
	void preTraversal()const;
	~AVLTree(){}
};

template <typename K,typename V,class CK>
node<K, V>* AVLTree<K, V, CK>::insert_aux(node *r,const K &k, const V &v)
{//插入辅助函数
	if (r == nullptr)//若已到叶子
		r = new node(k, v);
	else if (compare(k, r->key))
	{//否则,若插入关键字较当前关键字小
		r->left = insert_aux(r->left, k, v);//递归向左子树插入
		if (getHeight(r->left) - getHeight(r->right) == 2)
		{//若插入后,左子树过高,失去平衡
			if (compare(k, r->left->key))//1、在当前树的左子树的左边插入
				r = rightRotate(r, r->left);//则一次右旋即可,并更新新根
			else r = leftRightRotate(r, r->left);//2、在当前树的左子树的右边插入,则先左旋,再右旋
		}
	}
	else if (compare(r->key,k))
	{//若插入关键字较大
		r->right = insert_aux(r->right, k, v);//则递归右子树插入
		if (getHeight(r->right) - getHeight(r->left) == 2)
		{//若右子树过高
			if (compare(r->right->key, k))//3、在右子树的右边插入
				r = leftRotate(r, r->right);
			else r = rightLeftRotate(r, r->right);//4、在右子树的左边插入
		}
	}
	r->height = MAX(getHeight(r->left), getHeight(r->right)) + 1;//最后更新该子树根的高度
	return r;
}

template <typename K,typename V,class CK>
node<K, V>* AVLTree<K, V, CK>::erase_aux(node *r, const K &k)
{//删除辅助
	if (r == nullptr) return nullptr;
	else if (compare(k, r->key))
	{//若关键字k较小
		r->left = erase_aux(r->left, k);//则递归左子树删除
		if (getHeight(r->right) - getHeight(r->left) == 2)
		{//若删除后,左子树过低
			if (r->right->right != nullptr)//a、若当前子树根有右孙子
				r = leftRotate(r, r->right);//则左旋
			else r = rightLeftRotate(r, r->right);//b、否则,先右旋,再左旋
		}
	}
	else if (compare(r->key, k))
	{//若关键字较大,与上述情况对称,不赘述
		r->right = erase_aux(r->right, k);
		if (getHeight(r->left) - getHeight(r->right) == 2)
		{
			if (r->left->left != nullptr)
				r = rightRotate(r, r->left);
			else r = leftRightRotate(r, r->left);
		}
	}
	else
	{//若正是要删除的关键字
		if (!(r->left == nullptr || r->right == nullptr))
		{//若该子树有两个孩子
			node *del;
			r->right = erase_successor(r->right,del);//则删除后继,del记下将要被删除的节点
			r->key = del->key;
			r->value = del->value;
			delete del;
		}
		else
		{//若至多有一个,则删除其本身
			node *child;
			if (r->left != nullptr)
				child = r->left;
			else child = r->right;
			delete r;
			return child;//并直接返回孩子
		}
	}
	r->height = MAX(getHeight(r->left), getHeight(r->right)) + 1;//更新当前根的高度
	return r;
}

template <typename K,typename V,class CK>
node<K, V>* AVLTree<K, V, CK>::erase_successor(node *curr,node *&del)
{//删除某节点后继,即curr子树的最小关键字节点
	if (curr->left == nullptr)
	{//若当前正是最小值节点
		del = curr;//则将删除它
		return curr->right;//返回其右孩子
	}
	else
	{//否则
		curr->left = erase_successor(curr->left,del);//递归删除最小值节点
		if (getHeight(curr->right) - getHeight(curr->left) == 2)
		{//若删除后左子树过低
			if (curr->right->right != nullptr)//则进行如下调整
				curr = leftRotate(curr, curr->right);
			else curr = rightLeftRotate(curr, curr->right);
		}
		return curr;
	}
}

template <typename K,typename V,class CK>
node<K, V>* AVLTree<K, V, CK>::locate_aux(node *r,const K &k)const
{
	if (r == nullptr)//若没有该关键字
		return nullptr;
	else if (compare(k, r->key))//小于则往左找
		return locate_aux(r->left, k);
	else if (compare(r->key, k))//大于则往右找
		return locate_aux(r->right, k);
	else return r;//等于则直接返回
}

template <typename K,typename V,class CK>
void AVLTree<K, V, CK>::destroy_aux(node *r)
{
	if (r == nullptr) return;
	if (r->left != nullptr)
		destroy_aux(r->left);
	if (r->right != nullptr)
		destroy_aux(r->right);
	delete r;
}

template <typename K,typename V,class CK>
inline node<K, V>* AVLTree<K, V, CK>::leftRotate(node *par, node *child)
{//左旋
	par->right = child->left;
	child->left = par;
	par->height = MAX(getHeight(par->left), getHeight(par->right)) + 1;//更新高度
	child->height = MAX(getHeight(child->left), getHeight(child->right)) + 1;
	return child;//返回新根
}

template <typename K,typename V,class CK>
inline node<K, V>* AVLTree<K, V, CK>::rightRotate(node *par, node *child)
{//右旋
	par->left = child->right;
	child->right = par;
	par->height = MAX(getHeight(par->left), getHeight(par->right)) + 1;
	child->height = MAX(getHeight(child->left), getHeight(child->right)) + 1;
	return child;
}

template <typename K,typename V,class CK>
inline node<K, V>* AVLTree<K, V, CK>::leftRightRotate(node *par, node *child)
{//左、右旋
	par->left = leftRotate(child, child->right);//先将左孩子绕右孙子左旋,并更新左孩子
	return rightRotate(par, par->left);//再将其绕新左孩子右旋
}

template <typename K,typename V,class CK>
inline node<K, V>* AVLTree<K, V, CK>::rightLeftRotate(node *par, node *child)
{//右、左旋
	par->right = rightRotate(child, child->left);
	return leftRotate(par, par->right);
}

template <typename K,typename V,class CK>
void AVLTree<K, V, CK>::inTraversal()const
{//中序遍历
	if (root == nullptr) return;
	AVLTree L(root->left,compare);
	L.inTraversal();
	root->print();
	AVLTree R(root->right, compare);
	R.inTraversal();
}

template <typename K,typename V,class CK>
void AVLTree<K, V, CK>::preTraversal()const
{//先序遍历
	if (root == nullptr) return;
	root->print();
	AVLTree L(root->left, compare);
	L.preTraversal();
	AVLTree R(root->right, compare);
	R.preTraversal();
}


int main()
{
	AVLTree<int, int> t;
	for (int i = 1; i <= 20; i += 2)
		t.insert(i, i * i);//键、值
	t.insert(10, 100);
	t.inTraversal();
	cout << "------------------------" << endl;
	t.preTraversal();
	cout << "------------------------" << endl;
	t.erase(1);
	t.erase(3);
	t.erase(5);
	t.erase(9);
	t.erase(11);
	t.erase(13);
	t.erase(100);//删除不存在的键
	t.inTraversal();
	cout << "------------------------" << endl;
	t.preTraversal();
	getchar();
	t.clear();
	return 0;
}





    原文作者:平衡二叉树
    原文地址: https://blog.csdn.net/zilingxiyue/article/details/46310079
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞