数据结构-平衡二叉树 旋转过程平衡因子分析 c和java代码实现对比
平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,同时,平衡二叉树必定是二叉排序树。
高度差可以用平衡因子bf来定义,我们用左子树的高度减去右子树的高度来表示bf,即-1<|bf|<1。
引入平衡二叉树是由于二叉排序树,在某些情况会导致树的高度一直的增加,比如一组有序的数据,在查找或创建时递归层级会很深,导致方法栈容易溢出。
平衡二叉树是通过旋转来缓解树高度增加过快的情况。
先介绍下最小不平衡节点的概念:插入一个节点之后,距离这个插入节点最近的不平衡节点就是最小不平衡节点。就是说在递归插入节点后,开始回溯,碰到的第一个不平衡的节点就是最小不平衡节点。
当最小不平衡节点右子树高则需要左旋,左子树高则需要右旋(还有些情况需要先对其左/右子树旋转)。
思考:
1、既然旋转是通过平衡因子|bf|>1来决定怎么旋转的,那么在旋转前这些平衡因子是什么时候赋值的呢?
2、旋转之后,哪些节点需要调整?,平衡因子又改如何调整呢?
下图只列出左子树高的几种情况,T表示最小不平衡节点,L表示其左子树,LR表示L的右子树,
为了形象用EH(0),LH(1),RH(-1)分别表示某一节点 左右子树相等、左子树高、右子树高三种情况。
根据L节点的左右子树高度差来确定直接右旋还是先左旋再右旋,因为L为最小不平衡子树的左子树,故不会出现L.bf=EH的情况。
一、L.bf=LH
右旋:
旋转之后T.bf=L.bf=EH
二、L.bf=RH
先左旋再右旋:
当L的平衡因子为-1时则需要先对L进行右旋,然后再对T进行左旋。根据LR的情况再分为下面三种(因为旋转两次,那么最后最小不平衡子树的根节点为LR,并且LR.bf=EH)
1、 LR=EH
旋转之后T.bf=L.bf=EH
2、 LR=LH
旋转之后T.bf=RH, L.bf=EH
3、 LR=RH
旋转之后T.bf=EH, L.bf=LH
我认为网上最容易懂的C语言版代码入下:
. #include "stdio.h"
. #include "stdlib.h"
. #include "io.h"
. #include "math.h"
. #include "time.h"
.
. #define OK 1
. #define ERROR 0
. #define TRUE 1
. #define FALSE 0
. #define MAXSIZE 100 /* 存储空间初始分配量 */
.
. typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
.
.
. /* 二叉树的二叉链表结点结构定义 */
. typedef struct BiTNode /* 结点结构 */
. {
. int data; /* 结点数据 */
. int bf; /* 结点的平衡因子 */
. struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
. } BiTNode, *BiTree;
.
.
. /* 对以p为根的二叉排序树作右旋处理, */
. /* 处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点 */
. void R_Rotate(BiTree *P)
. {
. BiTree L;
. L=(*P)->lchild; /* L指向P的左子树根结点 */
. (*P)->lchild=L->rchild; /* L的右子树挂接为P的左子树 */
. L->rchild=(*P);
. *P=L; /* P指向新的根结点 */
. }
.
. /* 对以P为根的二叉排序树作左旋处理, */
. /* 处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0 */
. void L_Rotate(BiTree *P)
. {
. BiTree R;
. R=(*P)->rchild; /* R指向P的右子树根结点 */
. (*P)->rchild=R->lchild; /* R的左子树挂接为P的右子树 */
. R->lchild=(*P);
. *P=R; /* P指向新的根结点 */
. }
.
. #define LH +1 /* 左高 */
. #define EH 0 /* 等高 */
. #define RH -1 /* 右高 */
.
. /* 对以指针T所指结点为根的二叉树作左平衡旋转处理 */
. /* 本算法结束时,指针T指向新的根结点 */
. void LeftBalance(BiTree *T)
. {
. BiTree L,Lr;
. L=(*T)->lchild; /* L指向T的左子树根结点 */
. switch(L->bf)
. { /* 检查T的左子树的平衡度,并作相应平衡处理 */
. case LH: /* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */
. (*T)->bf=L->bf=EH;
. R_Rotate(T);
. break;
. case RH: /* 新结点插入在T的左孩子的右子树上,要作双旋处理 */
. Lr=L->rchild; /* Lr指向T的左孩子的右子树根 */
. switch(Lr->bf)
. { /* 修改T及其左孩子的平衡因子 */
. case LH: (*T)->bf=RH;
. L->bf=EH;
. break;
. case EH: (*T)->bf=L->bf=EH;
. break;
. case RH: (*T)->bf=EH;
. L->bf=LH;
. break;
. }
. Lr->bf=EH;
. L_Rotate(&(*T)->lchild); /* 对T的左子树作左旋平衡处理 */
. R_Rotate(T); /* 对T作右旋平衡处理 */
. }
. }
.
. /* 对以指针T所指结点为根的二叉树作右平衡旋转处理, */
. /* 本算法结束时,指针T指向新的根结点 */
. void RightBalance(BiTree *T)
. {
. BiTree R,Rl;
. R=(*T)->rchild; /* R指向T的右子树根结点 */
. switch(R->bf)
. { /* 检查T的右子树的平衡度,并作相应平衡处理 */
. case RH: /* 新结点插入在T的右孩子的右子树上,要作单左旋处理 */
. (*T)->bf=R->bf=EH;
. L_Rotate(T);
. break;
. case LH: /* 新结点插入在T的右孩子的左子树上,要作双旋处理 */
. Rl=R->lchild; /* Rl指向T的右孩子的左子树根 */
. switch(Rl->bf)
. { /* 修改T及其右孩子的平衡因子 */
. case RH: (*T)->bf=LH;
. R->bf=EH;
. break;
. case EH: (*T)->bf=R->bf=EH;
. break;
. case LH: (*T)->bf=EH;
. R->bf=RH;
. break;
. }
. Rl->bf=EH;
. R_Rotate(&(*T)->rchild); /* 对T的右子树作右旋平衡处理 */
. L_Rotate(T); /* 对T作左旋平衡处理 */
. }
. }
.
. /* 若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 */
. /* 数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树 */
. /* 失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。 */
. Status InsertAVL(BiTree *T,int e,Status *taller)
. {
. if(!*T)
. { /* 插入新结点,树“长高”,置taller为TRUE */
. *T=(BiTree)malloc(sizeof(BiTNode));
. (*T)->data=e; (*T)->lchild=(*T)->rchild=NULL; (*T)->bf=EH;
. *taller=TRUE;
. }
. else
. {
. if (e==(*T)->data)
. { /* 树中已存在和e有相同关键字的结点则不再插入 */
. *taller=FALSE; return FALSE;
. }
. if (e<(*T)->data)
. { /* 应继续在T的左子树中进行搜索 */
. if(!InsertAVL(&(*T)->lchild,e,taller)) /* 未插入 */
. return FALSE;
. if(*taller) /* 已插入到T的左子树中且左子树“长高” */
. switch((*T)->bf) /* 检查T的平衡度 */
. {
. case LH: /* 原本左子树比右子树高,需要作左平衡处理 */
. LeftBalance(T); *taller=FALSE; break;
. case EH: /* 原本左、右子树等高,现因左子树增高而使树增高 */
. (*T)->bf=LH; *taller=TRUE; break;
. case RH: /* 原本右子树比左子树高,现左、右子树等高 */
. (*T)->bf=EH; *taller=FALSE; break;
. }
. }
. else
. { /* 应继续在T的右子树中进行搜索 */
. if(!InsertAVL(&(*T)->rchild,e,taller)) /* 未插入 */
. return FALSE;
. if(*taller) /* 已插入到T的右子树且右子树“长高” */
. switch((*T)->bf) /* 检查T的平衡度 */
. {
. case LH: /* 原本左子树比右子树高,现左、右子树等高 */
. (*T)->bf=EH; *taller=FALSE; break;
. case EH: /* 原本左、右子树等高,现因右子树增高而使树增高 */
. (*T)->bf=RH; *taller=TRUE; break;
. case RH: /* 原本右子树比左子树高,需要作右平衡处理 */
. RightBalance(T); *taller=FALSE; break;
. }
. }
. }
. return TRUE;
. }
.
. int main(void)
. {
. int i;
. int a[]={,,,,,,,,,};
. BiTree T=NULL;
. Status taller;
. for(i=;i<;i++)
. {
. InsertAVL(&T,a[i],&taller);
. }
. printf("本样例建议断点跟踪查看平衡二叉树结构");
. return ;
. }
首先整体看一遍InsertAVL函数代码,结合上面的图,我想之前两个问题都会有答案了,再重复下。
1、既然旋转是通过平衡因子|bf|>1来决定怎么旋转的,那么在旋转前这些平衡因子是什么时候赋值呢?
在元素插入之后,首先认为该元素就是一个子树,从无到有,故高度增加taller=true,然后开始回溯到上一个节点根据元素插入后,对其的三种影响来调整平衡因子,同时重新赋值taller。
2、旋转之后,哪些节点需要调整?平衡因子又改如何调整呢?
因为平衡二叉树本身也是排序树,就上图以左子树高为列,若能直接右旋,影响的节点有T、L,最后会为T.bf=L.bf=EH。
若要先左旋再右旋,则根据LR的取值再分三种情况(最后会变成LR为根,LR.bf=EH)
1、 LR.bf=EH:旋转后 T.bf=L.bf=EH
2、 LR.bf=LH:旋转后 T.bf=RH,L.bf=EH
3、 LR.bf=RH:旋转后 T.bf=EH,L.bf=LH
右子树高的情况同理。
要是你对java实现不兴趣,可以不用往下看了,数据结构主要是其思想,实现只需要根据语言特性来稍作改变。
java代码:
思路跟上面代码一样(原谅我偷懒不写注释...),主要是多了个rootAVL和preNode。原因是c语言函数传值可以直接传指针,这样对于参数的修改会反应到被调函数外面。而java都是值传递,方法内操作的只是引用的一个副本,他们指向的地址相同而已,只能修改引用所指向的内存,修改引用副本是不会影响引用本身的。
故需要单独处理节点的前驱和根节点, rootAVL用来保存最后的根节点,因为每次插入都需要从根节点开始递归。
preNode表示每次回溯时的前面一个节点。
public class AVL { private boolean taller=false;
private Node root =null;
private static final int EH=0;
private static final int LH=1;
private static final int RH=-1; private class Node{
public int data;
public Node leftChild;
public Node rightChild;
public int balanceFactor; public Node(int data){
this.data=data;
balanceFactor=0;
}
} public Node RRotate(Node T){
Node temp=T.leftChild;
T.leftChild=temp.rightChild;
temp.rightChild=T;
return temp;
} public Node LRotate(Node T){
Node temp=T.rightChild;
T.rightChild=temp.leftChild;
temp.leftChild=T;
return temp;
} public Node leftBalance(Node node,Node preNode){
Node child=node.leftChild;
Node root=null;
switch (child.balanceFactor){
case LH:
node.balanceFactor=child.balanceFactor=EH;
root=RRotate(node);
if(preNode!=null && node.data< preNode.data){
preNode.leftChild=root;
}
if(preNode!=null && node.data> preNode.data){
preNode.rightChild=root;
}
break;
case RH:
Node rchild=child.rightChild;
switch (rchild.balanceFactor){
case EH:
node.balanceFactor=child.balanceFactor=EH;
break;
case LH:
node.balanceFactor=RH;
child.balanceFactor=EH;
break;
case RH:
node.balanceFactor=EH;
child.balanceFactor=LH;
break;
default:break;
}
rchild.balanceFactor=EH;
node.leftChild=LRotate(child);
root=RRotate(node);
if(preNode!=null && node.data<preNode.data){
preNode.leftChild=root;
}
if(preNode!=null && node.data>preNode.data){
preNode.rightChild=root;
}
break;
default:break;
}
return root;
} public Node rightBalance(Node node,Node preNode){
Node child=node.rightChild;
Node root=null;
switch (child.balanceFactor){
case RH:
node.balanceFactor=child.balanceFactor=EH;
root=LRotate(node);
if(preNode!=null && node.data<preNode.data){
preNode.leftChild=root;
}
if(preNode!=null && node.data>preNode.data){
preNode.rightChild=root;
}
break;
case LH:
Node lchild=child.leftChild;
switch (lchild.balanceFactor){
case EH:
node.balanceFactor=child.balanceFactor=EH;
break;
case RH:
node.balanceFactor=LH;
child.balanceFactor=EH;
break;
case LH:
node.balanceFactor=EH;
child.balanceFactor=RH;
break;
default:break;
}
lchild.balanceFactor=EH;
node.rightChild=RRotate(child);
root=LRotate(node);
if(preNode!=null && node.data<preNode.data){
preNode.leftChild=root;
}
if( preNode!=null && node.data>preNode.data){
preNode.rightChild=root;
}
break;
default:break;
}
return root;
} public boolean insertNode(int value){
return insertNode(root,value,null);
} public boolean insertNode(Node node, int value, Node preNode){
if(node==null){
node=new Node(value);
node.balanceFactor=EH;
taller=true;
if(preNode!=null && node.data< preNode.data){
preNode.leftChild=node;
}
if(preNode!=null && node.data> preNode.data){
preNode.rightChild=node;
}
root =node;
return true;
}else{
if(value==node.data){
root =node;
return false;
}
if (value<node.data){
if (!insertNode(node.leftChild, value, node)) {
root =node;
return false;
}
if(taller){
switch (node.balanceFactor){
case EH:taller=true;node.balanceFactor=LH;break;
case RH:taller=false;node.balanceFactor=EH;break;
case LH:
taller=false;
node=leftBalance(node,preNode);
if(preNode!=null){
node=preNode;
}
break;
default:break;
}
}
}
if (value>node.data){
if (!insertNode(node.rightChild, value,node)) {
root =node;
return false;
}
if(taller){
switch (node.balanceFactor){
case EH:taller=true;node.balanceFactor=RH;break;
case LH:taller=false;node.balanceFactor=EH;break;
case RH:
taller=false;
node=rightBalance(node,preNode);
if(preNode!=null){
node=preNode;
}
break;
default:break;
}
}
}
}
root =node;
return true;
} public void inorderTraversal(){
inorderTraversal(root);
} public void inorderTraversal(Node root){
if(root!=null){
inorderTraversal(root.leftChild);
System.out.println("节点:"+root.data+" 平衡因子:"+root.balanceFactor);
inorderTraversal(root.rightChild);
}
return ;
} public static void main(String[] args) {
//int[] data={8,6,4};
//int[] data={8,6,9,5,7,3};
//int[] data={8,6,7};
//int[] data={8,5,9,4,6,7};
//int[] data={8,5,9,4,7,6};
int[] data={8,5,9,7,6};
AVL avl=new AVL();
for(int i=0;i<data.length;i++){
avl.insertNode(data[i]);
}
avl.inorderTraversal();
}
}
数据结构-平衡二叉树 旋转过程平衡因子分析 c和java代码实现对比的更多相关文章
- 求小球反弹高度,及落地过程中经过的路程~~~java代码
总结:这种思想,不是一想就突然出现在脑海里的 package com.c2; //题目:一球从100米高度自由落下,每次落地后反跳回原高度的一半: //再落下,求它在 第10次落地时,共经过多少米?第 ...
- 数据结构与算法16—平衡二叉(AVL)树
我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度O(log2n)同时也由此而决定.但是,在某些极端的情况下(如在 ...
- 什么是泛型?,Set集合,TreeSet集合自然排序和比较器排序,数据结构-二叉树,数据结构-平衡二叉树
==知识点== 1.泛型 2.Set集合 3.TreeSet 4.数据结构-二叉树 5.数据结构-平衡二叉树 ==用到的单词== 1.element[ˈelɪmənt] 要素 元素(软) 2.key[ ...
- 详细分析链表的数据结构的实现过程(Java 实现)
目录 链表的数据结构的实现过程(Java 实现) 前言 基本概念 链表的基本结构 链表的基本操作的实现 在链表中添加元素 在链表头添加元素 在链表指定位置处添加元素 链表的虚拟头节点 链表的查询和修改 ...
- 详细分析栈和队列的数据结构的实现过程(Java 实现)
目录 栈和队列的数据结构的实现过程(Java 实现) 栈的数据结构的实现 栈的基础知识回顾 栈的常见应用 基于数组的栈的实现 具体代码设计 基于数组的栈简单的时间复杂度分析 关于栈的一个算法应用:括号 ...
- Java代码执行过程概述
Java代码经历三个阶段:源代码阶段(Source) -> 类加载阶段(ClassLoader) -> 运行时阶段(Runtime) 首先我们来理清一下Java代码整个执行过程, 让我们对 ...
- java代码的初始化过程研究
刚刚在ITeye上看到一篇关于java代码初始化的文章,看到代码我试着推理了下结果,虽然是大学时代学的知识了,没想到还能做对.(看来自己大学时掌握的基础还算不错,(*^__^*) 嘻嘻……)但 ...
- java代码的编译、执行过程
Java代码编译是由Java源码编译器来完成,流程图如下所示: Java字节码的执行是由JVM执行引擎来完成,流程图如下所示: Java代码编译和执行的整个过程包含了以下三个重要的机制: Java源码 ...
- 使用100%面向过程的思维方式来写java程序
1.java是强制写class关键字的语言,不能有独立的函数游离在类外出现在文件中,这和python c++ 都不同,后面的都可以单独在类外写函数,所以java被称为是纯面向对象的语言,py和c++都 ...
随机推荐
- 10个 jQuery 小技巧
10个 jQuery 小技巧 -----整理by: xiaoshuai 1. 返回顶部按钮 可以利用 animate 和 scrollTop 来实现返回顶部的动画,而不需要使用其他插件. // Bac ...
- (转)android从应用到驱动之—camera(1)---程序调用流程
一.开篇 写博客还得写开篇介绍,可惜,这个不是我所擅长的.就按我自己的想法写吧. 话说camera模块,从上层到底层一共包含着这么几个部分: 1.apk------java语言 2.camera的ja ...
- mac 安装软件提示权限不足的解决的方法
假设直接输入命令提示没权限的时候 1.sudo 安装命令 2.sudo su - 这时候切换到root用户下了 ,能够随心所欲了
- 如何kill掉一个screen
两种方式: 1.使用screen名字,kill掉. screen -S session_name -X quit 2.激活screen: screen -r session_name 并利用exit退 ...
- java 问题
1. 在ezmorph包中 有个 引用类时 写法为import [Z; 为什么加个[看不懂
- hdu 1058:Humble Numbers(动态规划 DP)
Humble Numbers Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)To ...
- 在visual studio中运行C++心得
1.在visual studio中建立C++项目 (1)新建->项目->空项目 C++ (2)右击项目->添加->新建项->C++文件(.app) (3编写C++文件 ...
- jQuery源码分析-each函数
本文部分截取自且行且思 jQuery.each方法用于遍历一个数组或对象,并对当前遍历的元素进行处理,在jQuery使用的频率非常大,下面就这个函数做了详细讲解: 复制代码代码 /*! * jQuer ...
- SQL TRIM()函数去除字符串头尾空格
SQL TRIM()函数去除字符串头尾空格 SQL 中的 TRIM 函数是用来移除掉一个字串中的字头或字尾.最常见的用途是移除字首或字尾的空白.这个函数在不同的资料库中有不同的名称: MySQL: T ...
- 【BZOJ4275】[ONTAK2015]Badania naukowe DP
[BZOJ4275][ONTAK2015]Badania naukowe Description 给定三个数字串A,B,C,请找到一个A,B的最长公共子序列,满足C是该子序列的子串. Input 第一 ...