二叉平衡树的创建
一些废话
最近在重温数据结构以应对即将到来的面试。发现很多当年学过的东西都忘掉了,就拿二叉平衡树来说,看到最后我才恍然大悟:哦,原来这东西我之前真的学过!而且貌似当时也写过测试的代码,只是没有保留下来。这次再整理一下,留在博客里以便查阅,同时也分享给大家,和大家交流一下。
再感慨一下,学过的东西,如果不经常复习时间长了真的跟没学过似的。大家应该都有这个感受吧,上次和耳关同学交流,她也有同感。所以应该学而时习之。不过话又说回来,如果不经常使用,谁会刻意去复习呢?总之还是多写代码吧!
不废话了,步入正题:平衡二叉树
为啥要整个二叉平衡树
首先,二叉平衡树是一种特殊的二叉查找树(也叫二叉排序树和二叉搜索树)。我们都利用二叉查找树进行查找操作的时间复杂度为O(h)其中h为二叉树的高度,而对二叉树进行的添加节点和删除节点操作都类似于查找操作,他们的时间复杂度都为O(h)。
我们又知道,对于一棵二叉树来说,如果节点为n,树高为h,那么有log2(n+1)<=h<=n,满树的时候h= log2(n+1)(当然,完全二叉树,或者还有一些不是完全二叉树的二叉树也可能满足这个条件),当二叉树退化成一个单链表时,h=n。简单一点就是说树高h最大为n最小为log2(n+1)。二叉查找树的各种操作的时间复杂度都和树高有关,所以为了提高时间效率,我们希望我们构造的这棵二叉树的树高越小越好,这就引出了平衡二叉树。
定义
平衡二叉树又称AVL树。它或者是颗空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。若将二叉树节点的平衡因子BF定义为该节点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有节点的平衡因子只可能为-1,0,1。只要二叉树上有一个节点的平衡因子的绝对值大于1,那么这颗平衡二叉树就失去了平衡。
正题!
首先来看看怎样创建一棵二叉排序树(不保证平衡)
我们可以先写插入节点的代码,然后创建二叉排序树就是把所有节点都插入。
比较简单,就是小的值放在左子,大的值放在右子,给出非递归的代码:
/**节点的数据结构**/ typedef struct b_node{ int value;//节点的值 struct b_node *l_tree;//左子树 struct b_node *r_tree;//右子树 } BNode,*PBNode; /** * 分配一个节点 * */ PBNode allocate_node() { PBNode node = NULL; node = (PBNode)malloc(sizeof(struct b_node)); if(node == NULL) return NULL; memset(node,0,sizeof(struct b_node)); return node; } /** * 设置一个节点的值 * */ void set_value(PBNode node,int value) { if(node == NULL) return; node->value = value; node->l_tree = NULL; node->r_tree = NULL; } /** * 向二叉查找树中添加一个节点,使得新的二叉树依然时二叉查找树 * 非递归方法实现 * */ void insert_node(PBNode *root,int value) { if(*root == NULL) { *root = allocate_node(); set_value(*root,value); } else { PBNode p = *root; PBNode pp = NULL;//保存父亲节点 bool is_left = false; while(p != NULL) { pp = p; is_left = false; if(value < p->value) { is_left = true; p = p->l_tree; } else if(value > p->value) { p = p->r_tree; } } PBNode node = allocate_node(); set_value(node,value); if(is_left) { pp->l_tree = node; } else { pp->r_tree = node; } } } /** * 插入法创建bst * */ void create_bst(PBNode *root,int value[],int len) { int i = 0; for(;i < len;i++) { insert_node(root,value[i]); } }
二叉平衡树的创建
二叉平衡树和普通的二叉排序树的区别。。。。当然是平衡啦!
所以在添加节点的时候,每添加一个节点之后,我们都要计算每棵子树的深度(高度),然后找出最小的非平衡子树,并对这棵子树进行调整,使之平衡,调整后整棵二叉树也就平衡了。
所以创建一棵平衡二叉树的关键,在于怎样将最小非平衡二叉树进行平衡。
什么是最小非平衡子树呢?
首先来说,一棵二叉排序树是否平衡就看他的左右子树的深度的差值,如果差值大于1,那么我们就说,这棵二叉排序(子)树不平衡!!
最小的非平衡子树就是沿着刚刚插入的节点向上(向根部)寻找,最先找到的非平衡子树就是最小非平衡子树。
怎样调整最小非平衡二叉树呢?
大家最好可以先看看有关的书籍,比如《算法导论》
在这里,我只写我的理解!(我并没有按照书上说的,什么旋转之类的,我是根据自己的理解写的,但实际上和书上的东西都是一样,就是加入了自己的一点理解)
首先,将最小非平衡子树分成四种类型:LL型、LR型、RL型和RR型。
有点儿懵?别着急慢慢解释。
如何判断类型?
判断类型的方法是,如果非平衡子树的左子树比右子树高,那么就是LX型,反之如果右子树较高就是RX型。然后,对于LX型,如果刚刚插入的节点的值value小于非平衡树树根的左孩子的值,则为LL型,反之为LR型。对于RX型,如果刚刚插入的节点的值value小于非平衡树树根的左孩子的值,则为RL型,反之为RR型。总之就是,比较左子树和右子树的高度确定第一个,比较刚刚插入的节点和树根的直接孩子的值确定第二个。
还是有点懵?
我要上图啦!!!!
LL型:
首先这个解释一下这个图,这个图是个示意图,并不一定准确。在这个图中,10是最小非平衡子树的根节点,8,或者8的子孙节点是刚刚插入的节点(图不是那么具体),总之就是插入了8或者8的子孙节点后以10为根的子树就不平衡了。
怎么说它是LL型呢?
因为:
1、 10的左子树(也就是以9为根节点的子树)比10的右子树高(再次说明,图片就是示意达到目的即可,并不是具体的图)
2、 刚刚插入的值(无论是8还是8.5或7.5)都比10的左孩子要小(根据二叉排序树的性质就可以知道,刚插入的节点位于9的左边所以一定比9小),为什么跟9比较呢?因为第一步已经确定是LX型啦!
LR型:
其中10为最小非平衡树的树根,9或者9的子孙为刚插入的节点(再次说明,图是为了说明情况,没有画全面、画具体,所以我用虚线表示了很多,实际上我们就认为10是最小非平衡子树的树根啊!!!!!)
为啥这是LR型呢?因为:
1、 10的左子树(也就是以8为根的子树)比10的右子树高。
2、 新插入的节点(无论是9还是9的子孙们)都比10的左孩子,也就是8要大(这一点根据二叉排序树的特点就可以知道,8的右子树一定比8大!!!!),为啥要跟8比较呢??因为第一步已经确定是LX型啦!!!(已经解释了两遍了,后面不解释啦!!!)
RL型:
其中8为最小非平衡树的根节点,9或者9的子孙为刚刚插入的节点
为啥是RL型呢??因为:
1、 8的右子树(也就是以10为根的子树)的高度比左子树高
2、 刚插入的节点值,无论是9还是9的子孙都比8的右孩子(10)大!!!
RR型:
其中8为最小非平衡树的根节点,10或者10的子孙为刚刚插入的节点。
为啥是RR型???因为:
1、 8的右子树比左子树高
2、 刚插入的值(10或10的子孙)比9大!!
接下来,,,重头戏!!!怎样调整到平衡!!!???
首先,我们重点关注的就是那三个带圈圈的节点!!!
第二,要明确,平衡的目的就是为了降低左右子树的高度差,但是要注意,调整后的树依然要是一棵二叉排序树,也就是节点的顺序不是随便排列的。把握住这两点,我们就可以调整啦!!!
第三,调整后,三个带圆圈的节点的分布形态是一个三角形
根据二叉排序树的性质可知,这三个节点中左子最小,根居中,右子最大。
了解了以上几点后,我们讲调整的步骤。
首先我们要确定三个节点的大小关系,大的用big表示,小的用small,不大不小的用middle表示。拿LL类型为例:
接下来开始调整:
1、 确定small、big、middle三个节点(就是上面的那个步骤)
2、 分配middle的左右子树给big和small
3、 将small和big作为middle的左右子树
4、 将parent指向middle,也就是将middle作为调整后的子树的根(注意如果最小非平衡子树的根节点是整棵树的根节点,那么此时parent为NULL,这是需要改变整棵树的树根为调整后的树根,也就是middle)
什么?还是有点懵?没关系,开始具体到每个类型,咱有四个类型要重复这些步骤呢,还怕搞不清?!
LL型:
步骤:
1、 确定big、middle和small(根据二叉排序树的性质确定)
我们假设最小非平衡子树的根节点,也就是10节点为unbalance,则
big = unbalance; middle = unbalance->l_tree; small = unbalance->l_tree->l_tree;
2、 将middle的左右子树分配给big和small
对于LL型,middle的左子不用分配,调整前后middle的左子都为small。对于middle的右子要分配给big,并且作为big的左子,这样才能保证大小顺位,也就是保证是二叉查找树。
big->l_tree = middle->r_tree;
3、 将small和big分别作为middle的左右子树
对于LL型,调整前small已经是middle的左子了,small不用调整
middle->r_tree = big;
4、
将middle作为调整后的根节点,也就是将middle作为parent的孩子
这里要分情况:
a)
如果parent为空,那么middle作为整棵树的树根,也就是root=middle
b)
如果parent不为空且unbalance为parent的左子,就将middle作为parent的左子
c)
如果parent不为空且unbalance为parent的右子,就将middle作为parent的右子。
LR型:
1、 确定big、middle和small(根据二叉排序树的性质确定)
我们假设最小非平衡子树的根节点,也就是10节点为unbalance,则
big = unbalance; small = unbalance->l_tree; middle = unbalance->l_tree->r_tree;
2、 将middle的左右子树分配给big和small
分配原则是,将middle的左子作为small的右子,将middle的右子作为big的左子
small->r_tree = middle->l_tree; big->l_tree = middle->r_tree;
3、 将small和big分别作为middle的左右子树
middle->l_tree = small; middle->r_tree = big;
4、
将middle作为调整后的根节点,也就是将middle作为parent的孩子
这里要分情况:(对于这一步,四种情况的做法都一样,所以后面就不重复啦!!!!!)
a)
如果parent为空,那么middle作为整棵树的树根,也就是root=middle
b)
如果parent不为空且unbalance为parent的左子,就将middle作为parent的左子
c)
如果parent不为空且unbalance为parent的右子,就将middle作为parent的右子。
RL型:
1、 确定big、middle和small(根据二叉排序树的性质确定)
我们假设最小非平衡子树的根节点,也就是10节点为unbalance,则
small = unbalance; big = unbalance->r_tree; middle = unbalance->r_tree->l_tree;
2、 将middle的左右子树分配给big和small
分配原则是,将middle的左子作为small的右子,将middle的右子作为big的左子
small->r_tree = middle->l_tree; big->l_tree = middle->r_tree;
3、 将small和big分别作为middle的左右子树
middle->l_tree = small; middle->r_tree = big;
4、
将middle作为调整后的根节点,也就是将middle作为parent的孩子
这里要分情况:(对于这一步,四种情况的做法都一样,所以后面就不重复啦!!!!!)
RR型:
1、 确定big、middle和small(根据二叉排序树的性质确定)
我们假设最小非平衡子树的根节点,也就是10节点为unbalance,则
small =unbalance; middle = unbalance->r_tree; big = unbalance->r_tree->r_tree;
2、
将middle的左右子树分配给big和small
对于RR型,middle的右子不用分配,调整前后middle的右子都为big。对于middle的左子要分配给small,并且作为small的右子,这样才能保证大小顺位,也就是保证是二叉查找树。
small->r_tree = middle->l_tree;
3、 将small和big分别作为middle的左右子树
对于RR型来说,调整前后big都是作为middle的右子,所以big不用调整
middle->l_tree = small;
4、
将middle作为调整后的根节点,也就是将middle作为parent的孩子
这里要分情况:(对于这一步,四种情况的做法都一样,所以后面就不重复啦!!!!!)
注意,我要上代码啦!!!
调整部分的具体代码(注意,这里我在每个节点中添加了一个指向其父节点的指针,便于处理)
/** * deal_unbalance调用的函数 * 根据最小非平衡树的类型进行调整,使其平衡 * unbalance为最小非平衡树的树根 * type为最小非平衡树的类型 * root为整棵树的树根,因为这个过程中,整棵树的树根可能都在随时变换 * */ void adjust(PBNode *root,PBNode unbalance,enum unbalance_type type) { int t = type; PBNode small; PBNode middle; PBNode big; switch (t) { case TYPE_LL: { //确定small、middle、big三个节点 big = unbalance; middle = unbalance->l_tree; small = unbalance->l_tree->l_tree; //分配middle节点的孩子,给small和big big->l_tree = middle->r_tree; //将small和big作为midlle的左子和右子 middle->r_tree = big; break; } case TYPE_LR: { //确定small、middle、big三个节点 big = unbalance; small = unbalance->l_tree; middle = unbalance->l_tree->r_tree; //分配middle节点的孩子,给small和big small->r_tree = middle->l_tree; big->l_tree = middle->r_tree; //将small和big作为midlle的左子和右子 middle->l_tree = small; middle->r_tree = big; break; } case TYPE_RL: { //确定small、middle、big三个节点 small = unbalance; big = unbalance->r_tree; middle = unbalance->r_tree->l_tree; //分配middle节点的孩子,给small和big small->r_tree = middle->l_tree; big->l_tree = middle->r_tree; //将small和big作为midlle的左子和右子 middle->l_tree = small; middle->r_tree = big; break; } case TYPE_RR: { //确定small、middle、big三个节点 small =unbalance; middle = unbalance->r_tree; big = unbalance->r_tree->r_tree; //分配middle节点的孩子,给small和big small->r_tree = middle->l_tree; //将small和big作为midlle的左子和右子 middle->l_tree = small; break; } } //将最小非平衡子树的父亲节点指向middle(也就是将middle,调整后的子树的根结点) if(unbalance->parent == NULL) //说明最小非平衡树的根节点就是整棵树的根结点 { printf("这里执行了!!!!!\n"); *root = middle; } else if(unbalance->parent->l_tree == unbalance)//根是父亲的左孩子 { unbalance->parent->l_tree = middle; } else if(unbalance->parent->r_tree == unbalance)//根是父亲的右孩子 { unbalance->parent->r_tree = middle; } //更改small、middle、big的父亲节点 middle->parent = unbalance->parent; big->parent = middle; small->parent = middle; }
最后,创建整个二叉平衡树的方法就是:
按照普通二叉排序树的要求一次插入各个节点,每插入一个节点要判断是否有平衡,如果不平衡要找出最小非平衡子树,并对最小非平衡子树进行调整。
为了完成创建,修改了一下节点的数据结构,添加了两个成员:deep表示以该节点为根的子树的深度,parent表示该节点的父节点,如下:
typedef struct b_node{ int value;//节点的值 int deep;//树的深度 struct b_node *l_tree;//左子树 struct b_node *r_tree;//右子树 struct b_node *parent;//父亲节点 } BNode,*PBNode;
还添加了两个函数,分别用于计算每棵子树的深度和判断以某个节点为根的子树是否平衡
/** * 计算每个子树的深度 * */ int compute_deep(PBNode root) { if(root == NULL) return 0;//空树的深度为0 int deep = 1; int left_deep = compute_deep(root->l_tree); int rigth_deep = compute_deep(root->r_tree); root->deep = deep + (left_deep > rigth_deep ? left_deep : rigth_deep); return root->deep; } /** * 判断以node节点为根的子树是否平衡 * */ bool is_balance(PBNode node) { if(node->l_tree == NULL) { if(node->r_tree == NULL) return true; return node->r_tree->deep == 2 ? false : true; } if(node->r_tree == NULL) { return node->l_tree->deep == 2 ? false : true; } return abs(node->l_tree->deep - node->r_tree->deep) == 2 ? false : true; }
为了判断是哪种类型,需要知道是右子树高还是左子树高,因此也加入了一个用于判断左右子树谁高的函数
//表示哪棵子树高 enum which_high{ LEFT_HIGH, RIGHT_HIGH }; /** *判断node节点的哪棵子树更高 **/ enum which_high get_higher(PBNode node) { if(node->l_tree == NULL ) return RIGHT_HIGH; if(node->r_tree == NULL) return LEFT_HIGH; if(node->l_tree->deep > node->r_tree->deep) return LEFT_HIGH; return RIGHT_HIGH; }
最后给出完整代码
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> #include <math.h> typedef struct b_node{ int value;//节点的值 int deep;//树的深度 struct b_node *l_tree;//左子树 struct b_node *r_tree;//右子树 struct b_node *parent;//父亲节点 } BNode,*PBNode; /** * 分配一个节点 * */ PBNode allocate_node() { PBNode node = NULL; node = (PBNode)malloc(sizeof(struct b_node)); if(node == NULL) return NULL; memset(node,0,sizeof(struct b_node)); return node; } /** * 设置一个节点的值 * */ void set_value(PBNode node,int value) { if(node == NULL) return; node->value = value; node->deep = -1; node->l_tree = NULL; node->r_tree = NULL; node->parent = NULL; } /** * 计算每个子树的深度 * */ int compute_deep(PBNode root) { if(root == NULL) return 0;//空树的深度为0 int deep = 1; int left_deep = compute_deep(root->l_tree); int rigth_deep = compute_deep(root->r_tree); root->deep = deep + (left_deep > rigth_deep ? left_deep : rigth_deep); return root->deep; } /** * 判断以node节点为根的子树是否平衡 * */ bool is_balance(PBNode node) { if(node->l_tree == NULL) { if(node->r_tree == NULL) return true; return node->r_tree->deep == 2 ? false : true; } if(node->r_tree == NULL) { return node->l_tree->deep == 2 ? false : true; } return abs(node->l_tree->deep - node->r_tree->deep) == 2 ? false : true; } //表示最小非平衡树可能的四种状态 enum unbalance_type { TYPE_LL, TYPE_LR, TYPE_RL, TYPE_RR }; /** * deal_unbalance调用的函数 * 根据最小非平衡树的类型进行调整,使其平衡 * unbalance为最小非平衡树的树根 * type为最小非平衡树的类型 * root为整棵树的树根,因为这个过程中,整棵树的树根可能都在随时变换 * */ void adjust(PBNode *root,PBNode unbalance,enum unbalance_type type) { int t = type; PBNode small; PBNode middle; PBNode big; switch (t) { case TYPE_LL: { //确定small、middle、big三个节点 big = unbalance; middle = unbalance->l_tree; small = unbalance->l_tree->l_tree; //分配middle节点的孩子,给small和big big->l_tree = middle->r_tree; //将small和big作为midlle的左子和右子 middle->r_tree = big; break; } case TYPE_LR: { //确定small、middle、big三个节点 big = unbalance; small = unbalance->l_tree; middle = unbalance->l_tree->r_tree; //分配middle节点的孩子,给small和big small->r_tree = middle->l_tree; big->l_tree = middle->r_tree; //将small和big作为midlle的左子和右子 middle->l_tree = small; middle->r_tree = big; break; } case TYPE_RL: { //确定small、middle、big三个节点 small = unbalance; big = unbalance->r_tree; middle = unbalance->r_tree->l_tree; //分配middle节点的孩子,给small和big small->r_tree = middle->l_tree; big->l_tree = middle->r_tree; //将small和big作为midlle的左子和右子 middle->l_tree = small; middle->r_tree = big; break; } case TYPE_RR: { //确定small、middle、big三个节点 small =unbalance; middle = unbalance->r_tree; big = unbalance->r_tree->r_tree; //分配middle节点的孩子,给small和big small->r_tree = middle->l_tree; //将small和big作为midlle的左子和右子 middle->l_tree = small; break; } } //将最小非平衡子树的父亲节点指向middle(也就是将middle,调整后的子树的根结点) if(unbalance->parent == NULL) //说明最小非平衡树的根节点就是整棵树的根结点 { printf("这里执行了!!!!!\n"); *root = middle; } else if(unbalance->parent->l_tree == unbalance)//根是父亲的左孩子 { unbalance->parent->l_tree = middle; } else if(unbalance->parent->r_tree == unbalance)//根是父亲的右孩子 { unbalance->parent->r_tree = middle; } //更改small、middle、big的父亲节点 middle->parent = unbalance->parent; big->parent = middle; small->parent = middle; } //表示哪棵子树高 enum which_high{ LEFT_HIGH, RIGHT_HIGH }; /** *判断node节点的哪棵子树更高 **/ enum which_high get_higher(PBNode node) { if(node->l_tree == NULL ) return RIGHT_HIGH; if(node->r_tree == NULL) return LEFT_HIGH; if(node->l_tree->deep > node->r_tree->deep) return LEFT_HIGH; return RIGHT_HIGH; } /** * 处理不平衡问题,其中value为刚刚添加的节点的值 * */ void deal_unbalance(PBNode *root,int value) { PBNode p = *root; PBNode unbalance = NULL; while(value != p->value) { //判断是否平衡 if(!is_balance(p)) { unbalance = p;//注意这里不能break,因为要找最小的非平衡子树,如果一旦找到非平衡子树就调出,则可能不是最小的 } if(value > p->value) { p = p->r_tree; } else if(value < p->value) { p = p->l_tree; } } if(unbalance != NULL)//说明有不平衡存在 { //调用处理不平衡问题的函数 if(get_higher(unbalance) == LEFT_HIGH)//左节点较高 { if(value < unbalance->l_tree->value)//说明为LL型 { adjust(root,unbalance,TYPE_LL); } else if(value > unbalance->l_tree->value)//说明为LR类型 { adjust(root,unbalance,TYPE_LR); } } else //右节点较高 { if(value < unbalance->r_tree->value)//说明为RL型 { adjust(root,unbalance,TYPE_RL); } else if(value > unbalance->r_tree->value)//说明为RR型 { adjust(root,unbalance,TYPE_RR); } } } } /** * 向二叉查找树中添加一个节点,使得新的二叉树依然时二叉查找树 * 非递归方法实现 * */ void insert_node(PBNode *root,int value) { if(*root == NULL) { *root = allocate_node(); set_value(*root,value); } else { PBNode p = *root; PBNode pp = NULL;//保存父亲节点 bool is_left = false; while(p != NULL) { pp = p; is_left = false; if(value < p->value) { is_left = true; p = p->l_tree; } else if(value > p->value) { p = p->r_tree; } } PBNode node = allocate_node(); set_value(node,value); node->parent = pp;//填父亲节点 if(is_left) { pp->l_tree = node; } else { pp->r_tree = node; } } //计算子树深度 compute_deep(*root); //处理不平衡问题 deal_unbalance(root,value); //处理完不平衡问题后还用重新计算子树深度 compute_deep(*root); } /** * 插入法创建bst * */ void create_bst(PBNode *root,int value[],int len) { int i = 0; for(;i < len;i++) { insert_node(root,value[i]); } } /** * 先序遍历二叉树 * */ void pre_traversal(PBNode root) { if(root == NULL) return; printf("%d ",root->value); pre_traversal(root->l_tree); pre_traversal(root->r_tree); } /** * 中序遍历二叉树 * */ void in_traversal(PBNode root) { if(root == NULL) return; in_traversal(root->l_tree); printf("%d ",root->value); in_traversal(root->r_tree); } /** * 查找值为vlue的节点 * */ PBNode get_node(PBNode root,int value) { if(root == NULL) return NULL; PBNode node = NULL; if(value < root->value) { node = get_node(root->l_tree,value); } else if(value > root->value) { node = get_node(root->r_tree,value); } else { node = root; } return node; } /** * 找到最小非平衡子树 * */ void free_node(PBNode *node); /** * 释放节点空间 * */ void free_node(PBNode *node) { if(*node == NULL) return; free(*node); *node = NULL; } /** * 销毁二叉树 * */ void destory_tree(PBNode *root) { if(*root == NULL) return; destory_tree(&((*root)->l_tree)); destory_tree(&((*root)->r_tree)); free_node(root); } int main() { int value[] = {7,4,6,3,12,5,1,14,10,8,9}; int len = 11; PBNode root = NULL; create_bst(&root,value,len); printf("先序序列为:"); pre_traversal(root); printf("\n"); printf("中序序列为:"); in_traversal(root); printf("\n"); destory_tree(&root);//释放资源 return 0; }
附上笔记的word文件和源代码文件
链接:http://pan.baidu.com/s/1slyjF6t 密码:k752