AVL平衡二叉树

首先思考一下,二叉搜索树的可能的缺陷是什么?

二叉搜索树在最差情况下,比如依次插入节点1、2、3、4、5、6、7....,查找的时间复杂度达到了O(N)(不限于查找,增删查改的时间复杂度在最差情况下都是O(N)),也就是说最差情况下,二叉搜索树的搜索时间复杂度和顺序查找是一样的。怎么解决呢?那就是让树尽可能平衡,试想上面的1、2、3、4、5、6、7,如果换成下图的方式,增删查改的时间复杂度是不是大幅下降:


显然,上图右边的显得更加"平衡",而左边则显得非常"线性"和"偏坠",。

对于二叉树,尽可能的树的平衡,可以达到更高的增删查改效率。

AVL树就是一种极端追求平衡的二叉树,它要求任何一个父节点,它的左子树和右子树的高度之差不可以超过1,如下都是不允许的:


如果在AVL树的插入、删除过程中,发生了上面的4种情况任意之一,那就AVL树就要调整节点来解决掉这种情况,这个调整的过程就叫翻转,对于上面的4种情况也有4种翻转方式:



1、所谓的右右翻转,中间节点2作为新父节点,原父节点1作为新父节点2的左子节点,2原先的左节点转而挂在节点1的右节点

3、所谓的左左翻转,中间节点2作为新父节点,原父节点3作为新父节点2的子节点,2原先的节点转而挂在节点1的节点

首先一定理解好左左/右右两个翻转。

2、所谓的左右翻转,子节点3需要首先做左左翻转,翻转后和图1的情况一样,再对父节点1进行右右翻转

4、所谓的右左翻转,子节点1需要首先做右右翻转,翻转后和图3的情况一样,再对父节点3进行左左翻转


AVL树在实际的插入/删除过程中,只会有上面的四种翻转的情况,简言之就是,AVL树在插入/删除一条数据后,都会判断是否插入/删除这条数据后,出现了上面四种情况任意之一,如出现,则做出翻转;同时,翻转后可能导致上级节点也出现不平衡的情况出现,那就继续翻转...保证整个AVL树都是肯定平衡的。

由上可得到如下重要结论:

1、AVL树任何时候都保证高度的平衡,即任意子树的父节点,左子树和右子树的高度差不超过1

1.1、这就保证了AVL树,增删查改的时间复杂度都是logN,最坏也都是logN。(AVL树保证查找最坏logN这个特性,对于不频繁插入/删除的场景来说确实优于红黑树)

1.2、但是要注意,插入/删除后可能要做最多logN次的翻转,简言之每一级的不平衡,都可能导致需要做翻转,这是AVL树之所以最终无过多实际用途的原因。

2、每次插入/删除时都要判断是否出现左子树和右子树的高度差,如何做到的?

每一个节点都记录自己的高度,在插入/删除后都(递归的)更新自己的高度,这样即可准确发现是否不平衡需要做翻转,翻转后也要(递归的)更新自己的高度

更直观的代码如下:

规定了节点的定义(node)和AVL树的方法,包括二叉树都有的前中后遍历(pre/mid/post)、4类翻转(ll/lr/rl/rr)、获取节点高度(height)、翻转(adjust)、查找(find)、删除(remove)、插入(insert)

template<class T> struct node {
	T data;
	int height;
	struct node *lchild;
	struct node *rchild;
	node(T _data, int _height, struct node * _lchild, struct node *_rchild): data(_data), height(_height), lchild(_lchild), rchild(_rchild) {}
};

template<class T> class avltree {
	node<T> *root;
	void show(T data);
	void free(node<T> *root);
	void pre(node<T> *node);
	void mid(node<T> *node);
	void post(node<T> *node);
	node<T> *ll(node<T> *node);
	node<T> *lr(node<T> *node);
	node<T> *rl(node<T> *node);
	node<T> *rr(node<T> *node);
	int height(node<T> *nd);
	node<T> *adjust(node<T> *nd);
	void update(node<T> *nd);
	node<T> *findmin(node<T> *rt);
	node<T> *del(node<T> *node, T data);
	node<T> *insert(node<T> *node, T data);
public:
	avltree(){}
	avltree(T *data, int size);
	~avltree();
	void add(T data);
	void remove(T data);
	node<T> *find(T data);
	void preshow();
	void midshow();
	void postshow();
	int get_height() {return root->height;}
};
下面是AVL树逻辑实现部分了:

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

//AVL树的构造, 其实也是节点依次插入的过程即insert的过程. 注意要保证根节点首先存在
template<class T> avltree<T>::avltree (T *data, int size) {
	root = 0;
	root = new node<T>(data[0], 1, 0, 0);
	for (int i = 1; i < size; i++) {
		root = insert(root, data[i]);
	}
}

//AVL树释放, 和所有二叉树是一样的
template<class T> void avltree<T>::free (node<T> *rt) {
	if (rt && !rt->lchild && !rt->rchild) {
		delete rt;
		rt = 0;
		return;
	}

	if (rt->lchild) {
		free(rt->lchild);
	}
	if (rt->rchild) {
		free(rt->rchild);
	}
}

template<class T> avltree<T>::~avltree () {
	free(root);
	root = 0;
}

template<class T> int avltree<T>::height (node<T> *nd) {
	return (nd)?nd->height:0;
}

//根据子节点的高度更新父节点高度
template<class T> void avltree<T>::update (node<T> *nd) {
	node<T> *lchild = nd->lchild, *rchild = nd->rchild;
	int height_l = height(lchild), height_r = height(rchild);
	nd->height = (height_l > height_r)?(height_l + 1):(height_r + 1);
}

//左旋. 父节点的左节点要提到父节点, 父节点转为其右子节点
template<class T> node<T> *avltree<T>::ll (node<T> *rt) {
	node<T> *tmp = rt->lchild;
	rt->lchild = tmp->rchild;
	tmp->rchild = rt;
	update(rt);
	update(tmp);
	return tmp;
}

//右左, 首先对左子节点做右旋, 然后再对父节点做左旋
template<class T> node<T> *avltree<T>::lr (node<T> *rt) {
	rt->rchild = ll(rt->rchild);
	node<T> *tmp = rt->rchild;
	rt->rchild = tmp->lchild;
	tmp->lchild = rt;
	update(rt);
	update(tmp);
	return tmp;
}

//左右, 首先对右子节点做左旋, 然后再对父节点做右旋
template<class T> node<T> *avltree<T>::rl (node<T> *rt) {
	rt->lchild = rr(rt->lchild);
	node<T> *tmp = rt->lchild;
	rt->lchild = tmp->rchild;
	tmp->rchild = rt;
	update(rt);
	update(tmp);
	return tmp;
}

//右旋. 父节点的右节点要提到父节点, 父节点转为其左子节点
template<class T> node<T> *avltree<T>::rr (node<T> *rt) {
	node<T> *tmp = rt->rchild;
	rt->rchild = tmp->lchild;
	tmp->lchild = rt;
	update(rt);
	update(tmp);
	return tmp;
}

//首先获取左子树高度和右子树的高度, 然后判断是否高度差已超过1, 如已超过再根据是四种情况中的哪种决定如何旋转
template<class T> node<T> *avltree<T>::adjust (node<T> *rt) {
//先获取到左子树高度height_l和右子树高度height_r
	node<T> *lchild = rt->lchild, *rchild = rt->rchild;
	int height_l = height(lchild), height_r = height(rchild);
	
//判断是否命中四种情况任意之一,还是没有命中,如命中应该是怎样翻转
	if (height_l - height_r >= 2) {
		if (lchild->lchild) {
			return ll(rt);//图3的情况
		} else {
			return rl(rt);//图4的情况
		}
	} else if (height_r - height_l >= 2) {
		if (rchild->rchild) {
			return rr(rt);//图1的情况
		} else {
			return lr(rt);//图2的情况
		}
	} else {
		return rt;
	}
}

//插入节点, 和二叉搜索树方式近似, 但在插入节点后需要首先调用update更新本节点的高度, 然后调用adjust判断是否需要翻转, 如需要则做出翻转
//返回给递归上一级的是可能翻转后的本层节点, 作为上一层节点的新的左/右子节点
template<class T> node<T> *avltree<T>::insert (node<T> *rt, T data) {
	if (rt) {
		T curdata = rt->data;
		if (curdata > data) {
			rt->lchild = insert(rt->lchild, data);
			update(rt);
			return adjust(rt);
		} else if (curdata < data) {
			rt->rchild = insert(rt->rchild, data);
			update(rt);
			return adjust(rt);
		} else {
			return rt;
		}
	} else {
		node<T> *newnode = new node<T>(data, 1, 0, 0);
		return newnode;
	}
}

//查找, 和二叉搜索树方式完全一样
template<class T> node<T> *avltree<T>::find (T data) {
	node<T> *rt = root;
	while (rt) {
		T curdata = rt->data;
		if (curdata == data) {
			return rt;
		} else if (curdata > data) {
			if (rt->lchild) {
				rt = rt->lchild;
			} else {
				return 0;
			}
		} else if (curdata < data) {
			if (rt->rchild) {
				rt = rt->rchild;
			} else {
				return 0;
			}
		}
	}

	return 0;
}

//必须注意的细节: 
//1、只有在待删除节点左右子节点都实际存在的情况下, 才会调用到这里, 所以不可能返回空
//2、实际返回情况分为3类: 
//  2.1、返回待删除节点的右子节点: 右子节点无左子节点的情况下
//  2.2、返回一个没有左子节点的节点(右子节点的左子节点): 右子节点的左子节点, 没有左子节点的情况
//  2.3、返回一个有左子节点的节点: 右子节点的左子树中的某节点, 这时实际要删除的是返回节点的左子节点
template<class T> node<T> *avltree<T>::findmin (node<T> *rt) {
	node<T> *rchild = rt->rchild;
	if (!rchild->lchild) {
		return rchild;
	}

	node<T> *rlchild = rchild->lchild;
	while (rlchild) {
		if (rlchild->lchild && rlchild->lchild->lchild) {
			rlchild = rlchild->lchild;
		} else {
			return rlchild;
		}
	}
}
//1、没找到...
//2、找到, 且没有左右子节点, 直接删
//3、找到, 只有左子节点或只有右子节点, 直接删, 但还要把左/右子树接上
//4、找到, 同时有左和右子节点, 根据上面的注释里的3种情况分别处理
//5、注意, 任何一种删除处理之后, 上级节点都必须更新高度, 以及可能需要做出的翻转
template<class T> node<T> *avltree<T>::del (node<T> *rt, T data) {
	if (rt) {
//任何删除后,上级父节点必须根据子节点的最新高度,更新自己的高度,判断是否需要做出翻转
		T curdata = rt->data;
		if (curdata > data) {
			rt->lchild = del(rt->lchild, data);
			update(rt);
			return adjust(rt);
		} else if (curdata < data) {
			rt->rchild = del(rt->rchild, data);
			update(rt);
			return adjust(rt);
		} else {
//左子树和右子树统统缺失,直接删
//左子树和右子树只有一个,直接删然后接上即可
//左子树和右子树都有,首先判断"替死鬼"节点是哪个(比待删除大的节点中最小的一个,findmin),


//同时判断属于3种情况中的哪种情况(右子节点/右子节点的唯一左子节点/右子节点的左子树中某节点,"替死鬼"节点的父节点),然后分别处理
			if (!rt->lchild && !rt->rchild) {
				delete rt;
				return 0;
			} else if (rt->lchild && !rt->rchild) {
				rt->data = rt->lchild->data;
				delete rt->lchild;
				rt->lchild = 0;
				update(rt);
				return rt;
			} else if (!rt->lchild && rt->rchild) {
				rt->data = rt->rchild->data;
				delete rt->rchild;
				rt->rchild = 0;
				update(rt);
				return rt;
			} else {
				node<T> *min = findmin(rt);
				if (min == rt->rchild) {
					rt->data = rt->rchild->data;
					rt->rchild = rt->rchild->rchild;
					delete rt->rchild;
					update(rt);
				} else if (min->lchild) {
					rt->data = min->lchild->data;
					delete min->lchild;
					min->lchild = 0;
				} else {
					rt->data = min->data;
					rt->rchild->lchild = min->rchild;
					delete min;
				}
				update(rt);
				return rt;
			}
		}
	}

	return 0;
}

//删除节点的外部接口
template<class T> void avltree<T>::remove (T data) {
	node<T> *nd = find(data);
	if (0 == nd) {
		return;
	}

	root = del(root, data);
}

template<class T> void avltree<T>::show (T data) {
	std::cout << data << " ";
}

template<class T> void avltree<T>::mid (node<T> *rt) {
	if (!rt) {
		return;
	}

	mid(rt->lchild);
	show(rt->data);
	mid(rt->rchild);
}

template<class T> void avltree<T>::midshow () {
	mid(root);
	std::cout << std::endl;
}

template<class T> void avltree<T>::pre (node<T> *rt) {
	if (!rt) {
		return;
	}

	show(rt->data);
	pre(rt->lchild);
	pre(rt->rchild);
}

template<class T> void avltree<T>::preshow () {
	pre(root);
	std::cout << std::endl;
}

template<class T> void avltree<T>::post (node<T> *rt) {
	if (!rt) {
		return;
	}

	post(rt->lchild);
	post(rt->rchild);
	show(rt->data);
}

template<class T> void avltree<T>::postshow () {
	post(root);
	std::cout << std::endl;
}

最后是测试程序:

#include "avl_funcss.h"
#include <stdlib.h>

using namespace std;

void init (int *testdata, int size) {
	srand((int)time(0));
	for (int i = 0; i < size; i++) {
		testdata[i] = rand() % 1000;
		std::cout << testdata[i];
		if (i != size - 1) {
			std::cout << ",";
		}
	}
	std::cout << std::endl << "inited" << std::endl;
}

int main () {
	int testdata[100] = {0};
	init(testdata, sizeof(testdata)/sizeof(testdata[0]));
	//int testdata[10] = {542,783,319,641,779,267,799,868,477,206};
	//int testdata[11] = {59,162,352,347,380,236,9,5,176,385,3};
	avltree<int> at(testdata, sizeof(testdata)/sizeof(testdata[0]));
	at.midshow();
/*
	at.midshow();
	at.preshow();
	at.remove(162);
	at.midshow();
	at.preshow();
	at.add(3);
	at.midshow();
	at.preshow();
*/
	return 0;
}

通过gdb或者调用遍历方法,可以清晰的感受到AVL树的插入、删除、查找的过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值