您的位置:首页 > 其它

算法学习笔记(七) 平衡二叉树 AVL树

2014-09-14 20:39 369 查看
AVL树是最先发明的自平衡二叉查找树, 其增删查时间复杂度都是 O(logn), 是一种相当高效的数据结构。当面对需要频繁查找又经常增删这种情景时,AVL树就非常的适用。

AVL树定义

AVL树诞生于 1962 年,由 G.M. Adelson-Velsky 和 E.M. Landis 发明。AVL树首先是一种二叉查找树。二叉查找树是这么定义的,为空或具有以下性质:

若它的左子树不空,则左子树上所有的点的值均小于其根节点;
若它的右子树不空,则右子树上所有的点的值均大于其根节点;
它的左右子树分别为二叉查找树;

AVL 树是具有以下性质的二叉查找树:

左子树和右子树的高度差(平衡因子)不能超过 1;
左子树和右子树都是 AVL树;

AVL树与红黑树对比

提到平衡二叉树的常用算法就不得不提红黑树,红黑树诞生于1972年,由 Rudolf Bayer 发明。比 AVL 树晚 10 年,由于其随机数据插入性能更好,获得了更为广泛的应用。如Java 的 TreeMap和C++ STL 的map,linux 内核中的用户态地址空间管理等都采用了红黑树算法。当然 AVL树也是很有应用价值的,比如32 位的 Windows 系统用户态地址空间管理就采用了AVL树,
毕竟 AVL的搜索性能还是很具优势的。

对比之前,再介绍一下红黑树的定义。在《算法导论》红黑树定义如下,红黑树是指一棵满足下述性质的二叉查找树:(1) 每个结点或者为黑色或者为红色; (2) 根结点为黑色; (3) 每个叶结点(实际上就是NULL指针)都是黑色的; (4) 如果一个结点是红色的,那么它的两个子节点都是黑色的(也就是说,不能有两个相邻的红色结点); (5)对于每个结点,从该结点到其所有子孙叶结点的路径中所包含的黑色结点数量必须相同;

AVL与红黑树不同之处:

红黑树对于数值随机插入性能更好,这种场景实际更常见,故应用更广泛;
AVL 树对于顺序数据插入更有优势;
红黑树部分平衡,降低了旋转的要求;
AVL 树高度平衡,查询操作更具优势;
AVL 树代码更为简单易实现;

小实验(C实现)

对于二叉树的相关算法,一定要有递归的思想,分析问题时,要会合理分析整体与局部。AVL树实现的核心在于插入数据后,如何维护其平衡。对于数据的插入,肯定通过递归来找到对应的叶节点,将对应数据插入。然后插入后,就是通过对应的旋转操作来维护其平衡。这种旋转有四种情形:

LL 型,节点的左孩子的左子树上插入一个新的节点,单右旋;
LR 型, 节点的左孩子的右子树上插入一个新的节点,双旋,先左旋后右旋;
RR 型,节点的右孩子的右子树上插入一个新的节点,单左旋;
RL 型,节点的右孩子的左子树上插入一个新的节点,双旋,先右旋后左旋;

代码 C语言实现:

#include <stdlib.h>
#include <stdio.h>

typedef int bool;
#define true 1
#define false 0

typedef int dataType;
typedef struct BiTNode {
dataType data;
int balance;  // 平衡因子 = 左子树深度 - 右子树深度,平衡二叉树取:-1, 0, 1
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

/* 左旋 */
void leftRotate(BiTree *T) {
BiTree rightTree = (*T)->rchild;
(*T)->rchild = rightTree->lchild;
rightTree->lchild = (*T);
(*T) = rightTree;
}

/* 右旋 */
void rightRotate(BiTree *T) {
BiTree leftTree = (*T)->lchild;
(*T)->lchild = leftTree->rchild;
leftTree->rchild = (*T);
(*T) = leftTree;
}

/* 左子树高,左平衡 */
void leftBalance(BiTree *T) {
BiTree leftTree = (*T)->lchild;
BiTree leftTreeRight = leftTree->rchild;
switch (leftTree->balance) {
case 1:  // 左子树平衡因子为 1, (插入左孩子左子树), 单右旋
(*T)->balance = 0;
leftTree->balance = 0;
rightRotate(T);
break;
case -1: //左子树平衡因子为 -1, (插入左孩子右子树), 双旋
switch (leftTreeRight->balance) {
case 1:
(*T)->balance = -1;
leftTree->balance = 0;
break;
case 0:
(*T)->balance = 0;
leftTree->balance = 0;
break;
case -1:
(*T)->balance = 0;
leftTree->balance = 1;
break;
}
leftTreeRight->balance = 0;
leftRotate(&((*T)->lchild));
rightRotate(T);
}
}

/* 右子树高,右平衡 */
void rightBalance(BiTree *T) {
BiTree rightTree = (*T)->rchild;
BiTree rightTreeLeft = rightTree->lchild;
switch (rightTree->balance) {
case -1:   //右子树平衡因子为 -1, (插入右孩子右子树), 单左旋
(*T)->balance = 0;
rightTree->balance = 0;
leftRotate(T);
break;
case 1:  //右子树平衡因子为 1, (插入右孩子左子树), 双旋
switch (rightTreeLeft->balance) {
case -1:
(*T)->balance = 1;
rightTree->balance = 0;
break;
case 0:
(*T)->balance = 0;
rightTree->balance = 0;
break;
case 1:
(*T)->balance = 0;
rightTree->balance = -1;
break;
}
rightTreeLeft->balance = 0;
rightRotate(&((*T)->rchild));
leftRotate(T);
}
}

/* AVL 树插入, 先定位, 再插入, 然后维持自平衡*/
bool insertAVL(BiTree *T, int elem, bool *taller) {
if (*T == NULL) {
*T = (BiTree) malloc(sizeof(BiTNode));
(*T)->data = elem;
(*T)->balance = 0;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
*taller = true;
return true;
}
if (elem == (*T)->data) {
*taller = false;
return false;
}
// 如果插入元素小于根节点,递推搜索左子树,插入后维持-左平衡
if (elem < (*T)->data) {
if (!insertAVL(&((*T)->lchild), elem, taller))
return false;
if (*taller) {
switch ((*T)->balance) {
case 1:
leftBalance(T);
*taller = false;
break;
case 0:
(*T)->balance = 1;
*taller = true;
break;
case -1:
(*T)->balance = 0;
*taller = false;
break;

}
}
}
// 如果插入元素大于根节点,递推搜索右子树, 插入后维持-右平衡
if (elem > (*T)->data) {
if (!insertAVL(&((*T)->rchild), elem, taller))
return false;
if (*taller) {
switch ((*T)->balance) {
case 1:
(*T)->balance = 0;
*taller = false;
break;
case 0:
(*T)->balance = -1;
*taller = true;
break;
case -1:
rightBalance(T);
*taller = false;
break;
}
}
}
return true;
}

void inOrderTraverse(BiTree T) {
if (T == NULL)	return;
inOrderTraverse(T->lchild);
printf("%d ", T->data);
inOrderTraverse(T->rchild);
}

void preOrderTraverse(BiTree T) {
if (T == NULL)	return;
printf("%d ", T->data);
preOrderTraverse(T->lchild);
preOrderTraverse(T->rchild);
}

int main() {
int a[10] = {3, 2, 1, 4, 5, 6, 7, 0, 9, 8};
int i;
bool taller;
BiTree T = NULL;
for (i = 0; i < 10; i++) {
insertAVL(&T, a[i], &taller);
}
printf("inOrderTraverse:  ");
inOrderTraverse(T);
printf("\npreOrderTraverse: ");
preOrderTraverse(T);
return 0;
}

/* 运行结果如下,由中序和前序遍历,可还原二叉树,验证是否准确
inOrderTraverse:  0 1 2 3 4 5 6 7 8 9
preOrderTraverse: 4 2 1 0 3 6 5 8 7 9
*/


【博客地址:http://blog.csdn.net/thisinnocence 】
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: