非线性数据结构 之 AVL树(1)
2013-02-18 07:47
330 查看
之前讨论过BST树,BST树不是一种平衡树,什么叫平衡树呢? 所谓的平衡指的是一棵树的左右两棵子树的高度差,如果高度差小于等于1,我们就认为是平衡的,否则就是不平衡的,试想一下,如果按顺序插入1,2,3,4,5,6,7这么几个数的话,就会构造出一棵右倾的BST树,所有的子节点都是其父节点的右孩子,这样的BST的搜索查找性能就被退化成线性的了。所以树的平衡性是衡量一棵树的最重要的指标。
平衡的BST树有很多不同的算法来实现,其中最著名的是所谓的AVL树,所以AVL树就是一种平衡二叉搜索树,下面来讨论它。
怎么维持一棵树的平衡呢? 根据定义,平衡就是要维持任意一棵树中的子树高度差小于等于1,我们在设计节点的时候,增加一个成员:树的高度。代码如下:
节点设计中,除了有左右孩子指针之外,增加了一个bf成员,这个成员一般被称为平衡因子,其实就是记录以该节点为根节点的子树的高度。
一棵树怎么会失去平衡呢?请看:
上图是一个典型的插入一个节点导致BST树失去平衡的例子,在框框中的子树里,对于根节点10来说,其左右子树的高度差已经达到2了,因此根据定义这不是一棵平衡树了,为了让一棵树保持平衡,我们首先总结所有出现不平衡的情况,再来看如何针对每一种情况使得二叉树重新得到平衡。
那么插入节点之后失去平衡的情况是怎么样的呢? 其实,情况总结起来只有两种,细分为四种,一图顶万言:
对于一棵二叉树,插入一个节点(黑色的新节点)导致不平衡的情况就是上图所示的四种情况,四种情况是对称的,所以只要理解了LL不平衡和LR不平衡,那么RR不平衡和RL不平衡都是一样的。
所谓的L(左)不平衡指的是,在插入之前,根节点(注意,树中的任意一个节点都可以是根节点,根节点是相对的)的左右子树高度差已经是1了(左子树高度 - 右子树高度),于是插入一个节点到红色根节点的左子树后,其左右子树的高度差就可能达到2。如果左右子树高度差确实达到2,那么情况分为两种:
第一,插入的节点比其左子树的根节点小引起的不平衡,叫做LL不平衡,LL就是Left-Left,也就是说这种不平衡是由左边的子树的左子树高度增加导致的,请对照图看。
第二,插入的节点比其左子树的根节点大引起的不平衡,叫做LR不平衡,LL就是Left-Right,也就是说这种不平衡是由左边的子树的右子树高度增加导致的,请对照图看。
对称地,对于R(右)不平衡来说,也分为RR不平衡和RL不平衡,就是插入的节点在根节点的右子树的右边,和插入的节点在根节点的右子树的左边。
下面来讨论如何重新平衡二叉搜索树,我们一个一个来看,先来看看LL不平衡:
从图可以看到,对于LL不平衡,我们只需要对发生不平衡的根节点(红色节点)进行一次“右旋转”即可,旋转之后,二叉树重新保持了平衡,而且红,白,黑三个节点的大小关系仍然保持,不会破坏二叉搜索树的定义。
那么这个所谓的右旋转是怎么实现的呢??看清楚一点你会发现,其实很简单,就修改两个指针即可,下图是更详细的右旋过程解剖图:
上图是所谓的右旋转的操作详解,对于一棵发生了LL不平衡的BST树来说,只需要针对根节点进行右旋,就可以恢复平衡。这个右旋转的操作代码如下:
对于上面的代码,第3,4,5行已经很明白了,第7,8行是平衡因子的重新计算,所谓的平衡因子就是一棵树的高度,我们将一棵树的高度(即深度)记录在其根节点中,那么当我们要衡量一棵树是否发生不平衡的时候就很方便了,只需要比较左右子树高度差是否小于等于1即可),那么一棵树的高度是多少呢?答案是:一棵树的高度,等于其左右子树的高度的最大值,再加1,(加上自己本身的高度)。
其中函数 height() 用来求一棵树的高度,代码如下:
而对于发生了RR不平衡的情况,对称地我们要进行左旋转,情况如下:
详细的左旋转步骤,跟右旋转是严格对称的:
其代码如下:
其实,旋转操作是二叉树的基本操作。弄懂了左旋转和右旋转之后,我们就可以很简单地解决所谓的LR不平衡和RL不平衡了,先来看LR不平衡,这种不平衡是由于插入的节点比左子树的根节点大而导致的,对于这种情况,我们如何恢复平衡呢? 请看下图:
对于LR不平衡,我们要进行两次旋转,第一次,从图2到图3,对发生LR不平衡的根节点的左子树节点进行左旋转,然后得到图4,其实图4的情况就是LL不平衡,此时根据上面的叙述,只需要对根节点进行右旋转即可,得到图5. 这时,红,白,黑三个节点的大小关系仍满足搜索树的特性,同时恢复了平衡。
其实现代码更简单,由于左旋转和右旋转已经实现了,这个操作其实就是左右旋转的结合而已:
完全对称地,所谓的RL不平衡,我们也要经过两次的旋转,将其重新平衡:
其代码如下:
上面费了那么大劲儿讲完AVL树的基本操作:左旋,右旋,左右旋和右左旋之后,可以来讨论其《插入》操作了。
AVL树本身就是一棵二叉搜索树,所以其插入操作跟BST树是很类似的,具体的思路如下:
1,按照BST树的插入算法,插入一个节点。
2,插入节点之后,判断树的平衡是否遭到破坏。(判断左右子树高度差)
3,如果确实发生了不平衡,确定是哪一种(上面所述四种之一:LL不平衡,RR不平衡,LR不平衡,RL不平衡)
4,针对发生的不平衡,采用响应的旋转来恢复平衡。
下面是代码:
AVL树的删除更加复杂一点,留到下一节介绍。
平衡的BST树有很多不同的算法来实现,其中最著名的是所谓的AVL树,所以AVL树就是一种平衡二叉搜索树,下面来讨论它。
怎么维持一棵树的平衡呢? 根据定义,平衡就是要维持任意一棵树中的子树高度差小于等于1,我们在设计节点的时候,增加一个成员:树的高度。代码如下:
typedef struct _tree_node { tn_datatype data; struct _tree_node *lchild; struct _tree_node *rchild; #ifdef BALANCE_FACTOR int bf; // for AVL #endif }treenode, *linktree;
节点设计中,除了有左右孩子指针之外,增加了一个bf成员,这个成员一般被称为平衡因子,其实就是记录以该节点为根节点的子树的高度。
一棵树怎么会失去平衡呢?请看:
上图是一个典型的插入一个节点导致BST树失去平衡的例子,在框框中的子树里,对于根节点10来说,其左右子树的高度差已经达到2了,因此根据定义这不是一棵平衡树了,为了让一棵树保持平衡,我们首先总结所有出现不平衡的情况,再来看如何针对每一种情况使得二叉树重新得到平衡。
那么插入节点之后失去平衡的情况是怎么样的呢? 其实,情况总结起来只有两种,细分为四种,一图顶万言:
对于一棵二叉树,插入一个节点(黑色的新节点)导致不平衡的情况就是上图所示的四种情况,四种情况是对称的,所以只要理解了LL不平衡和LR不平衡,那么RR不平衡和RL不平衡都是一样的。
所谓的L(左)不平衡指的是,在插入之前,根节点(注意,树中的任意一个节点都可以是根节点,根节点是相对的)的左右子树高度差已经是1了(左子树高度 - 右子树高度),于是插入一个节点到红色根节点的左子树后,其左右子树的高度差就可能达到2。如果左右子树高度差确实达到2,那么情况分为两种:
第一,插入的节点比其左子树的根节点小引起的不平衡,叫做LL不平衡,LL就是Left-Left,也就是说这种不平衡是由左边的子树的左子树高度增加导致的,请对照图看。
第二,插入的节点比其左子树的根节点大引起的不平衡,叫做LR不平衡,LL就是Left-Right,也就是说这种不平衡是由左边的子树的右子树高度增加导致的,请对照图看。
对称地,对于R(右)不平衡来说,也分为RR不平衡和RL不平衡,就是插入的节点在根节点的右子树的右边,和插入的节点在根节点的右子树的左边。
下面来讨论如何重新平衡二叉搜索树,我们一个一个来看,先来看看LL不平衡:
从图可以看到,对于LL不平衡,我们只需要对发生不平衡的根节点(红色节点)进行一次“右旋转”即可,旋转之后,二叉树重新保持了平衡,而且红,白,黑三个节点的大小关系仍然保持,不会破坏二叉搜索树的定义。
那么这个所谓的右旋转是怎么实现的呢??看清楚一点你会发现,其实很简单,就修改两个指针即可,下图是更详细的右旋过程解剖图:
上图是所谓的右旋转的操作详解,对于一棵发生了LL不平衡的BST树来说,只需要针对根节点进行右旋,就可以恢复平衡。这个右旋转的操作代码如下:
linktree right_rotate(linktree root) { linktree p = root->lchild; // 使得 p 指向root的左孩子 root->lchild = p->rchild; // 使得root的左孩子指针指向p的右孩子,上图1 p->rchild = root; // 使得p的右孩子指针指向root,上图2 // 下面是平衡因子(即树的高度)的重新计算 root->bf = MAX(height(root->lchild), height(root->rchild)) + 1; p->bf = MAX(height(p->lchild), root->bf) + 1; return p; }
对于上面的代码,第3,4,5行已经很明白了,第7,8行是平衡因子的重新计算,所谓的平衡因子就是一棵树的高度,我们将一棵树的高度(即深度)记录在其根节点中,那么当我们要衡量一棵树是否发生不平衡的时候就很方便了,只需要比较左右子树高度差是否小于等于1即可),那么一棵树的高度是多少呢?答案是:一棵树的高度,等于其左右子树的高度的最大值,再加1,(加上自己本身的高度)。
其中函数 height() 用来求一棵树的高度,代码如下:
int height(linktree root) { return (root == NULL) ? 0 : root->bf; // 假如root是空树,那么高度为0,否则直接返回bf(bf就是树的高度) }
而对于发生了RR不平衡的情况,对称地我们要进行左旋转,情况如下:
详细的左旋转步骤,跟右旋转是严格对称的:
其代码如下:
linktree left_rotate(linktree root) { linktree p = root->rchild; root->rchild = p->lchild; p->lchild = root; root->bf = MAX(height(root->lchild), height(root->rchild)) + 1; p->bf = MAX(root->bf, height(p->rchild)) + 1; return p; }
其实,旋转操作是二叉树的基本操作。弄懂了左旋转和右旋转之后,我们就可以很简单地解决所谓的LR不平衡和RL不平衡了,先来看LR不平衡,这种不平衡是由于插入的节点比左子树的根节点大而导致的,对于这种情况,我们如何恢复平衡呢? 请看下图:
对于LR不平衡,我们要进行两次旋转,第一次,从图2到图3,对发生LR不平衡的根节点的左子树节点进行左旋转,然后得到图4,其实图4的情况就是LL不平衡,此时根据上面的叙述,只需要对根节点进行右旋转即可,得到图5. 这时,红,白,黑三个节点的大小关系仍满足搜索树的特性,同时恢复了平衡。
其实现代码更简单,由于左旋转和右旋转已经实现了,这个操作其实就是左右旋转的结合而已:
linktree left_right_rotate(linktree root) { root->lchild = left_rotate(root->lchild); return right_rotate(root); }
完全对称地,所谓的RL不平衡,我们也要经过两次的旋转,将其重新平衡:
其代码如下:
linktree right_left_rotate(linktree root) { root->rchild = right_rotate(root->rchild); // 对其右子树进行右旋转,上图2 return left_rotate(root); // 对根节点进行左旋转,上图4 }
上面费了那么大劲儿讲完AVL树的基本操作:左旋,右旋,左右旋和右左旋之后,可以来讨论其《插入》操作了。
AVL树本身就是一棵二叉搜索树,所以其插入操作跟BST树是很类似的,具体的思路如下:
1,按照BST树的插入算法,插入一个节点。
2,插入节点之后,判断树的平衡是否遭到破坏。(判断左右子树高度差)
3,如果确实发生了不平衡,确定是哪一种(上面所述四种之一:LL不平衡,RR不平衡,LR不平衡,RL不平衡)
4,针对发生的不平衡,采用响应的旋转来恢复平衡。
下面是代码:
linktree AVL_insert(linktree root, tn_datatype data) { // 以下是BST树的插入算法 if(root == NULL) return new_node(data, NULL, NULL); if(data < root->data) root->lchild = AVL_insert(root->lchild, data); else if(data > root->data) root->rchild = AVL_insert(root->rchild, data); else { printf("%d is already exist.\n", data); } // 以下是有别于BST树的额外的操作 if(height(root->lchild) - height(root->rchild) == 2) // 发生左不平衡,下面进一步确认不平衡类型 { if(data < root->lchild->data) // LL不平衡,使用右旋转 root = right_rotate(root); else if(data > root->lchild->data) //LR不平衡,使用左旋转 root = left_rotate(root); } else if(height(root->rchild) - height(root->lchild) == 2) { if(data > root->rchild->data) root = left_rotate(root); else if(data < root->rchild->data) root = right_rotate(root); } root->bf = MAX(height(root->lchild), height(root->rchild)) + 1; return root; }
AVL树的删除更加复杂一点,留到下一节介绍。
相关文章推荐
- 非线性数据结构 之 AVL树(2)
- ※数据结构※→☆非线性结构(tree)☆============树 顺序存储结构(tree sequence)(十五)
- 数据结构14.自平衡二叉查找树_AVL树
- 【数据结构】AVL树详解
- 动手实现 数据结构 之 “AVL树”
- 数据结构_非线性结构_图
- java数据结构-非线性结构之树
- 数据结构全攻略--攻破非线性结构的堡垒之树和二叉树(二)
- 数据结构全攻略--攻破非线性结构的堡垒之哈弗曼树篇
- 数据结构之AVL树
- 数据结构14.自平衡二叉查找树_AVL树
- 【郝斌数据结构自学笔记】57-59_递归8 _ 汉诺塔_1线性结构总复习 2线性结构和非线性结构关系 3栈队列链表数组之间的关系【重点】
- 【数据结构】AVL树
- 数据结构查找(2)--平衡的二叉查找树(AVL树)
- 数据结构-AVL树的旋转
- 【数据结构】红黑树/AVL树的分析
- 数据结构 非线性结构 树 介绍及存储方法
- 数据结构:AVL树
- 数据结构--AVL树
- [置顶] ※数据结构※→☆非线性结构(tree)☆============树结点 链式存储结构(tree node list)(十四)