简介
自平衡二叉树(AVL)属于二叉平衡树的一类,此类树主要完成一个从键到值的查找过程,即字典(或映射),它维护树高度的方式与其他数据结构不同。
自平衡规则:
- AVL树的左、右子树都是AVL树
- 左、右子树的高度差不超过1
在数据结构中,最常见的数据间关系的抽象就是集合(Collection)和字典(Dictionary)。
集合就是线性表(元素允许重复),而字典是一种非多键映射关系(键不允许重复)。
对集合而言,一个班中的所有学生构成一个集合,可以是有序的(有序集合)也可以是无序的(无序集合),查找的时间复杂度一般是O(n),很低。集合的典型就是C#中的List,STL中的vector。集合主要用于存储数据,很少用于查找。如要用于查找,那么可以选择基于有序表的二分查找,相应时间复杂度是O(logn),而要想从无序表转变为有序表,就是各种排序算法的使命了。
而对字典而言,如同根据一个学生的学号找到他这次考试的成绩一样,主要是用于查找的。这是从键到值的单值映射,键不能重复,值可以重复。根据键找到值是字典的工作。如果是平衡查找树,就为O(logn),实现一般以红黑树为主(比AVL简单),如Java的TreeMap、STL的map;如果是哈希表,就为O(1),如Java的HashMap。
源码
树的建立
在这里,只讲元素的插入,而对于删除操作,由于其复杂性,暂且不能实现。
结点的数据结构为:
template<class T> struct AVLNode { T data; AVLNode *lchild, *rchild; int BF; // 平衡因子 };
前面与二叉树一样,BF用于累计高度差,因为查询高度是一个枯燥的递归操作。
创建结点:
template<class T, class N> N* AVLTree<T, N>::New() { N* newp = BiTree<T, N>::New(); newp->BF = 0; return newp; }
按数组插入:
template<class T, class N> AVLTree<T, N>::AVLTree(const vector<T>& s) { if (s.empty()) throw "数组不能为空"; root = new N; root->data = s[0]; root->BF = 0; root->lchild = NULL; root->rchild = NULL; for (int i = 1; i < s.Length(); i++) Insert(s[i]); }
结点的插入
对于AVL,结点的插入是一项复杂的工作。由于插入操作,树原本的平衡被破坏,需要重新调整。
基本步骤是:
- 二分查找,找到相应的空位置,并插入(若已存在则忽略),同时存储查找路径
- 从路径逆着向上找到第一个最小不平衡子树的根结点的父结点,接下来就是对这个子树进行调整
- 调整子树,有LL、LR、RL、RR四种情况
1)顺藤摸瓜
stack<N*> path; // 存储插入前的查找路径(便于回溯) ////////////////////////////////////////////////////////////////////////// // 插入操作 N *p = root; while (true) { path.push(p); if (newp->data < p->data) // 插入值小于根结点,入左子树 { if (p->lchild != NULL) { p = p->lchild; // 值小于LL,则递归入L } else { p->lchild = newp; break; // 根结点无左孩子,正好插入 } } else if (newp->data > p->data) // 插入值大于根结点,入右子树 { if (p->rchild != NULL) { p = p->rchild; // 值大于RR,则递归入R } else { p->rchild = newp; break; // 根结点无右孩子,正好插入 } } else // 插入值等于根结点,返回 { delete newp; return false; } }
2)找到“罪魁祸首”
// 调整插入路径上结点的BF,定位失衡的结点*p及其父结点*parent N *child = NULL; // *child作为*p的孩子结点 p = newp; N *parent = path.top(); // *parent是*p的父结点 path.pop(); while (true) { if (parent->lchild == p) // p是parent的左孩子,那么parent的BF++ parent->BF++; // BF的变化:-1->0->1->2 else // p是parent的右孩子,那么parent的BF-- parent->BF--; // BF的变化:1->0->-1->-2 if (parent->BF == 2 || parent->BF == -2) // *parent是失衡结点(第一个|BF|>=2) break; // 找到最小不平衡子树的根结点 if (parent->BF == 0) // 在插入新结点后,*parent的左右子树高度相等(BF为0),说明以*parent为根的子树高度未增加 return true; // 所以路径中的其余祖先结点无需调整BF if (path.empty()) // 直到树的根结点,在path中没有结点|BF|超出2,所以不必调整(导致有BF为+1,-1等下次插入再行调整) return true; child = p; p = parent; parent = path.top(); // 由path向上回溯 path.pop(); }
3)各居各位
////////////////////////////////////////////////////////////////////////// // *parent失衡,以下代码进行调整 N *ancestor = path.empty() ? NULL : path.top(); // ancestor为parent的双亲结点 if (!path.empty()) path.pop(); ////////////////////////////////////////////////////////////////////////// if (parent->BF == 2 && p->lchild == child) // LL { // 前 // A(parent,root),BF(2) // | // B(p),BF(1)__________|_____AR // | // BL(X)_____|_____BR // 后 // B(p,root),BF(0) // | // BL(X)_____|___________________A(parent),BF(0) // | // BR_____|_____AR parent->lchild = p->rchild; p->rchild = parent; parent->BF = 0; p->BF = 0; if (ancestor != NULL) { if (ancestor->lchild == parent) // 修改p的双亲为ancestor ancestor->lchild = p; else ancestor->rchild = p; } else { root = p; } return true; } ////////////////////////////////////////////////////////////////////////// if (parent->BF == -2 && p->rchild == child) // RR { // 前 // A(parent,root),BF(-2) // | // AL_____|___________________| // B(p),BF(-1) // BL_____|_____BR(X) // 后 // B(p,root),BF(0) // | // A(parent),BF(0)_____|_____BR(X) // | // AL_____|_____BL parent->rchild = p->lchild; // 和LL只有这两句不一样 p->lchild = parent; parent->BF = 0; p->BF = 0; if (ancestor != NULL) { if (ancestor->lchild == parent) // 修改p的双亲为ancestor ancestor->lchild = p; else ancestor->rchild = p; } else { root = p; } return true; } ////////////////////////////////////////////////////////////////////////// if (parent->BF == 2 && p->rchild == child) // LR { // 前 // A(parent,root),BF(2) // | // B(p),BF(-1)_________|_____AR // | // BL_____|_____C(pc),BF(1) // | // CL(X)______|______CR // 后(减少一层) // C(pc,root),BF(0) // | // B(p),BF(0)____|_____A(parent),BF(-1) // | | // BL_____|_____CL(x) CR_____|_____AR N *pc = p->rchild; parent->lchild = pc->rchild; p->rchild = pc->lchild; pc->lchild = p; pc->rchild = parent; pc->BF = 0; p->BF = 0; parent->BF = -1; if (ancestor != NULL) { if (ancestor->lchild == parent) // 修改pc的双亲为ancestor ancestor->lchild = pc; else ancestor->rchild = pc; } else { root = pc; } return true; } ////////////////////////////////////////////////////////////////////////// if (parent->BF == -2 && p->lchild == child) // RL { // 前 // A(parent,root),BF(-2) // | // AL_____|___________________B(p),BF(1) // | // C(pc),BF(-1)__|______BR // | // CL_____|_____CR(X) // 后(减少一层) // C(pc,root),BF(0) // | // A(parent),BF(1)|_____B(p),BF(0) // | | // AL_____|_____CL CR(x)_____|_____BR N *pc = p->lchild; parent->rchild = pc->lchild; p->lchild = pc->rchild; pc->lchild = parent; pc->rchild = p; pc->BF = 0; p->BF = 0; parent->BF = 1; if (ancestor != NULL) { if (ancestor->lchild == parent) // 修改pc的双亲为ancestor ancestor->lchild = pc; else ancestor->rchild = pc; } else { root = pc; } return true; } return true; }
调整过程全部是旋转操作
总结
由于AVL操作的复杂性,因而不常用AVL,现在主要使用红黑树(RBT),它们的区别是:
- AVL讲究整体平衡,因而要求严格、操作复杂
- RBT追求局部平衡,因而实现较AVL简单
时间: 2024-10-10 20:27:26