浅谈平衡搜索树之AVL树
2017-11-05 14:21
489 查看
对于上篇的二叉搜索树,我们可以讨论一下它的时间复杂度。在最好的情况下,也就是这颗二叉搜索树是一棵完全二叉树的情况下,它的查找效率是lgN(以2为底N的对数,这里为了方便起见这样写),但是在最坏的情况下,也就是说,在插入的时候是有序插入,这颗二叉搜索树达到了以下的状态
这个时候这颗二叉搜索是一棵极度不平衡的树,它的时间复杂度是N。显然如果这颗树存了10亿个数据,那么最坏的情况要查找10亿次,而对于lgN这种算法,只需要30次就够了,两者的查找效率显而易见。所以我们在二叉搜索树的基础上,提出了二叉平衡搜索树的概念。本篇暂时只讲二叉平衡搜索树的一个分支AVL树,在下一篇我们将讲到另一个分支红黑树。
AVL树的特点
AVL树右称为高度平衡的二叉搜索树,它能够保持二叉树的高度平衡,减少搜索长度,那么它是怎么来实现的呢?
1.首先它是一棵二叉搜索树,它具有二叉搜索树所具有的所有性质。
2.左右子树的高度之差的绝对值不超过1
3.树中的每个左子树和右子树都是AVL树
4.每个节点多了一个平衡因子(_bf)的概念,它等于右子树的高度减去左子树的高度,它的绝对值不超过1(即为1,-1,0)。
若有N个数,这颗树的高度可以保持在lgN,对于插入查找删除的操作效率都是lgN。
AVL树的增(insert)
对于AVL树,增是它的重点也是它的难点,是它最难理解的地方。会了增,其实删除也是很好理解的。所以本篇我们只讨论增,以及它在增的时候是如何保持它的高度平衡的。
1.首先和二叉搜索树一样的是,一样也是比较插入的节点与当前节点的key值,找到一个为空的位置然后进行插入。
2.如果父亲的左树插入的,平衡因子_bf–;右树增加的,平衡因子 _bf++。
3.插入了节点,会影响它祖先这一条线上的高度,我们通过parent的_bf在通过调整之后的值,来作出相应的平衡调整。
①._bf变为0,说明原来的 _bf是1或-1,子树的高度没有变。
②._bf变为1或-1,说明原来的 _bf是0,子树的高度变了,需要继续向上调整。
③._bf变为2或-2,说明原来的 _bf是1或-1,这个时候就需要通过旋转来调整树的高度。
下面来仔细介绍一下旋转这种方法。
1.左旋
如上图所示,10的_bf原来是1,20的 _bf原来是0,在20的右树插入一个节点之后,10的 _bf变为了2,20的 _bf变为了1,这个时候就要以10这个节点为轴进行左旋,将b给10的右,10变成20的左,这样一旋转之后,两者的平衡因子都变成了0。由上图可知,发生左旋的条件是parent的 _bf是2,parent的右孩子的 _bf是1。
2.右旋
右旋与左旋的原理是相似的,这里就不再重复了,只是方向反了一下。发生右旋的条件是parent的_bf是-2,parent的左孩子的 _bf是-1。
3.右左双旋
顾名思义,如上图,右左双旋就是先以30为轴进行一个右旋,再以10为轴进行一个左旋。这有三种情况,一种就是在如上图在c树插入了一个节点改变了高度,另一种就是在b树插入了一个节点,还有一种是20就是新增节点,b、c都不存在。可以发现的是,进行右左双旋的条件是,parent的_bf是2,parent的右孩子的 _bf是-1。
4.左右双旋
与右左双旋类似,先进行左旋,再进行右单旋。也是有三种情况,b插入,c插入,20是新增节点。进行左右双旋的条件是,parent的_bf是-2,parent的左孩子的 _bf是1。
下面我们通过代码来实现一棵AVL树。
这里我们除了实现了一个Insert,还实现了一个判平衡的函数Isbalance。在代码中我们可以看到,用注释注释了的判平衡的算法效率很低,它的思想有一点像用递归方法求斐波那契数列,会有重复求解的过程,效率非常低,所以这里只写出来并不用。
最终测试的代码如下,我们每插入一个值就判断这棵树平不平衡。
这个时候这颗二叉搜索是一棵极度不平衡的树,它的时间复杂度是N。显然如果这颗树存了10亿个数据,那么最坏的情况要查找10亿次,而对于lgN这种算法,只需要30次就够了,两者的查找效率显而易见。所以我们在二叉搜索树的基础上,提出了二叉平衡搜索树的概念。本篇暂时只讲二叉平衡搜索树的一个分支AVL树,在下一篇我们将讲到另一个分支红黑树。
AVL树的特点
AVL树右称为高度平衡的二叉搜索树,它能够保持二叉树的高度平衡,减少搜索长度,那么它是怎么来实现的呢?
1.首先它是一棵二叉搜索树,它具有二叉搜索树所具有的所有性质。
2.左右子树的高度之差的绝对值不超过1
3.树中的每个左子树和右子树都是AVL树
4.每个节点多了一个平衡因子(_bf)的概念,它等于右子树的高度减去左子树的高度,它的绝对值不超过1(即为1,-1,0)。
若有N个数,这颗树的高度可以保持在lgN,对于插入查找删除的操作效率都是lgN。
AVL树的增(insert)
对于AVL树,增是它的重点也是它的难点,是它最难理解的地方。会了增,其实删除也是很好理解的。所以本篇我们只讨论增,以及它在增的时候是如何保持它的高度平衡的。
1.首先和二叉搜索树一样的是,一样也是比较插入的节点与当前节点的key值,找到一个为空的位置然后进行插入。
2.如果父亲的左树插入的,平衡因子_bf–;右树增加的,平衡因子 _bf++。
3.插入了节点,会影响它祖先这一条线上的高度,我们通过parent的_bf在通过调整之后的值,来作出相应的平衡调整。
①._bf变为0,说明原来的 _bf是1或-1,子树的高度没有变。
②._bf变为1或-1,说明原来的 _bf是0,子树的高度变了,需要继续向上调整。
③._bf变为2或-2,说明原来的 _bf是1或-1,这个时候就需要通过旋转来调整树的高度。
下面来仔细介绍一下旋转这种方法。
1.左旋
如上图所示,10的_bf原来是1,20的 _bf原来是0,在20的右树插入一个节点之后,10的 _bf变为了2,20的 _bf变为了1,这个时候就要以10这个节点为轴进行左旋,将b给10的右,10变成20的左,这样一旋转之后,两者的平衡因子都变成了0。由上图可知,发生左旋的条件是parent的 _bf是2,parent的右孩子的 _bf是1。
2.右旋
右旋与左旋的原理是相似的,这里就不再重复了,只是方向反了一下。发生右旋的条件是parent的_bf是-2,parent的左孩子的 _bf是-1。
3.右左双旋
顾名思义,如上图,右左双旋就是先以30为轴进行一个右旋,再以10为轴进行一个左旋。这有三种情况,一种就是在如上图在c树插入了一个节点改变了高度,另一种就是在b树插入了一个节点,还有一种是20就是新增节点,b、c都不存在。可以发现的是,进行右左双旋的条件是,parent的_bf是2,parent的右孩子的 _bf是-1。
4.左右双旋
与右左双旋类似,先进行左旋,再进行右单旋。也是有三种情况,b插入,c插入,20是新增节点。进行左右双旋的条件是,parent的_bf是-2,parent的左孩子的 _bf是1。
下面我们通过代码来实现一棵AVL树。
#pragma once #include <iostream> using namespace std; #include <assert.h> template <class K,class V> struct AVLTreeNode { K _key; V _value; int _bf; AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; AVLTreeNode(const K& key, const V& value) :_left(NULL) , _right(NULL) , _parent(NULL) , _key(key) , _value(value) , _bf(0) {} }; template <class K,class V> class AVLTree { typedef AVLTreeNode<K, V> Node; public: AVLTree() :_root(NULL) {} bool Insert(const K& key, const V& value) { if (_root == NULL) { _root = new Node(key, value); return true; } Node* cur = _root; 4000 Node* parent = NULL; while (cur) { if (cur->_key < key) { parent = cur; cur = cur->_right; } else if (cur->_key > key) { parent = cur; cur = cur->_left; } else { return false; } } cur = new Node(key, value); //让parent的左或右指向cur,cur的parent指向parent if (parent->_key < key) { parent->_right = cur; cur->_parent = parent; } else { parent->_left = cur; cur->_parent = parent; } //更新平衡因子 while (parent) { //左增加,_bf-- if (cur == parent->_left) { parent->_bf--; } else //右增加,_bf++ { parent->_bf++; } //根据parent更新后的平衡因子调整 if (parent->_bf == 0) //子树高度没变,不用调整,直接退出 { break; } else if (parent->_bf == 1 || parent->_bf == -1) //子树高度变了,继续向上调整 { cur = parent; parent = cur->_parent; } else if (parent->_bf == 2 || parent->_bf == -2) //进行旋转 { if (parent->_bf == 2) { if (cur->_bf == 1) { RotateL(parent);//左旋 } else { RotateRL(parent);//右左双旋 } } else { if (cur->_bf == -1) { RotateR(parent);//右旋 } else { RotateLR(parent);//左右双旋 } } //旋转之后不用调整了,直接跳出循环 break; } else //走到这肯定是不平衡了,断言失败 { assert(false); } } return true; } bool IsBalance() { //return _IsBalance(_root); int height = 0; return _IsBalance(_root,height); } void Inorder() { _Inorder(_root); cout << endl; } protected: bool _IsBalance(Node* root) //效率慢 { if (root == NULL) { return true; } //右树的高度-左树的高度和bf比较 int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); return abs(rightHeight - leftHeight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right); } bool _IsBalance(Node* root, int& height) { if (root == NULL) { height = 0; return true; } int leftHeight = 0; if (_IsBalance(root->_left, leftHeight) == false) //只要有一个节点的平衡因子不对立马返回错误,不用再算后面的 { return false; } int rightHeight = 0; if (_IsBalance(root->_right, rightHeight) == false) { return false; } if (root->_bf != rightHeight - leftHeight) //只要平衡因子不等于右树高度-左树高度就是错误 { cout << "平衡因子异常" << root->_key << endl; return false; } height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; //返回左树和右树高的那个的高度+1 return abs(leftHeight - rightHeight) < 2; } int _Height(Node* root) { if (root == NULL) { return 0; } int left = _Height(root->_left); int right = _Height(root->_right); return left > right ? left + 1 : right + 1; //返回左树和右树高的那个的高度+1 } void RotateR(Node* parent)//右旋 { Node* subL = parent->_left; Node* subLR = subL->_right; Node* grandparent = parent->_parent; parent->_left = subLR; if (subLR) //如果subLR存在,则将它的父亲指向parent { subLR->_parent = parent; } subL->_right = parent; parent->_parent = subL; if (grandparent == NULL) //parent是根节点 { _root = subL; subL->_parent = NULL; } else { if (parent == grandparent->_left) { grandparent->_left = subL; } else { grandparent->_right = subL; } subL->_parent = grandparent; } //更新它们的平衡因子 subL->_bf = parent->_bf = 0; } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; Node* grandparent = parent->_parent; parent->_right = subRL; if (subRL) { subRL->_parent = parent; } subR->_left = parent; parent->_parent = subR; if (grandparent == NULL) { _root = subR; subR->_parent = NULL; } else { if (grandparent->_left == parent) { grandparent->_left = subR; } else { grandparent->_right = subR; } subR->_parent = grandparent; } subR->_bf = parent->_bf = 0; } void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(subR); RotateL(parent); if (bf == -1) //在subRL的左边插入了一个节点 { parent->_bf = 0; subR->_bf = 1; subRL->_bf = 0; } else if (bf == 1) //在subRL的右边插入了一个节点 { parent->_bf = -1; subR->_bf = 0; subRL->_bf = 0; } else if (bf == 0) //subRL是新增节点 { parent->_bf = 0; subR->_bf = 0; subRL->_bf = 0; } else { assert(false); } } void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(subL); RotateR(parent); if (bf == -1) //在subLR的左边插入了一个节点 { parent->_bf = 0; subL->_bf = 1; subLR->_bf = 0; } else if (bf == 1) //在subLR的右边插入了一个节点 { parent->_bf = -1; subL->_bf = 0; subLR->_bf = 0; } else if (bf == 0) //subLR是新增节点 { parent->_bf = 0; subL->_bf = 0; subLR->_bf = 0; } else { assert(false); } } bool _Inorder(Node* root) { if (root == NULL) { return false; } _Inorder(root->_left); cout << root->_key << " "; _Inorder(root->_right); return true; } private: Node* _root; }; void TestAVLTree() { int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; AVLTree<int, int> t; for (size_t i = 0; i < sizeof(a) / sizeof(int); ++i) { t.Insert(a[i], i); t.Inorder(); cout << "IsBalance()?" << t.IsBalance() << endl; } }
这里我们除了实现了一个Insert,还实现了一个判平衡的函数Isbalance。在代码中我们可以看到,用注释注释了的判平衡的算法效率很低,它的思想有一点像用递归方法求斐波那契数列,会有重复求解的过程,效率非常低,所以这里只写出来并不用。
最终测试的代码如下,我们每插入一个值就判断这棵树平不平衡。
相关文章推荐
- 数据结构-平衡搜索二叉树(AVL树)
- 【平衡搜索树】AVL树
- 平衡搜索树---AVL树
- 二叉平衡搜索树——AVL树
- 平衡搜索树-AVL树
- 浅谈WordPress的五大搜索优化功能
- 高效的平衡搜索树——红黑树
- [转]浅谈算法和数据结构: 八 平衡查找树之2-3树
- 浅谈智能搜索和对话式OS
- 【原创】浅谈搜索-中(dfs)(红与黑,Dungeon Master)
- 平衡搜索树
- AVL树的旋转平衡
- 搜索背后的奥秘——浅谈语义主题计算
- 搜索背后的奥秘——浅谈语义主题计算
- 【转】浅谈算法和数据结构: 九 平衡查找树之红黑树
- 浅谈如何在MySQL中进行模糊搜索的一些问题
- 搜索背后的奥秘——浅谈语义主题计算
- 浅谈京东搜索关键词排名规则
- 搜索背后的奥秘——浅谈语义主题计算
- AVL树(平衡二叉查找树)