【算法】论平衡二叉树(AVL)的正确种植方法
平衡二叉树的由来
普通二叉搜索树的缺陷
get(根据key获取val)
max(获取最大key),
min(获取最小key)
floor(对key向下取整)
ceiling(对key向上取整)
rank(获取给定key的排名)
select(根据排名获得给定key)
put (插入键值对)
delete(删除键值对)
BST的动态方法可能会修改二叉树的结构, 使其结点分布不均匀,使得在下一步的操作中, 静态方法和动态方法都变得更为低效。
插入的顺序影响二叉搜索树的构造




为什么二叉搜索树会变得低效?
插入和删除操作都可能降低未来操作的性能
什么是平衡二叉树






AVL和普通BST区别在于动态方法
平衡二叉树的监督机制




为每个结点设置并维护height属性
/**
* @Author: HuWan Peng
* @Date Created in 10:35 2017/12/29
*/
public class AVL {
Node root; // 根结点
private class Node {
int key,val;
Node left,right;
int height = 1; // 每个结点的高度属性
public Node (int key, int val) {
this.key = key;
this.val = val;
}
}
// 编写API方法
}
- 在插入结点时(put), 沿插入的路径更新结点的高度值(不一定会加1 !只是要重新计算)
- 在删除结点时(delete),沿删除的路径更新结点的高度值(不一定减1! 只是要重新计算)
- 在发现二叉树变得不平衡的时候, 通过“旋转”使其平衡, 这时候要更新相关结点的高度值(具体的我下面会详细讲)
/**
* @description: 返回两个数中的最大值
*/
private int max (int a, int b) {
return a>b ? a : b;
}
/**
* @description: 获得当前结点的高度
*/
private int height (Node x) {
if(x == null) return 0;
return x.height;
}
// 下面的insert方法是简化后的代码
public Node insert (Node x, int key, int val) {
其他代码 。。。。
insert(x.left, key, val); // 进行递归的插入
x.height = max(height(x.left),height(x.right)) + 1; // 更新结点的height属性(沿着递归路径)
return x;
}
x.height = max(height(x.left),height(x.right)) + 1;
计算BF以监督平衡二叉树的状态
/**
* @description: 获得平衡因子
*/
private int getBalance (Node x) {
if(x == null) return 0;
return height(x.left) - height(x.right);
}
平衡二叉树的修正机制
左旋和右旋






/**
* @description: 右旋方法
*/
private Node rotateRight (Node x) {
Node y = x.left; // 取得x的左儿子
x.left = y.right; // 将x左儿子的右儿子("拖油瓶"结点)链接到旋转后的x的左链接中
y.right = x; // 调转x和它左儿子的父子关系,使x成为它原左儿子的右子树
x.height = max(height(x.left),height(x.right)) + 1; // 更新并维护受影响结点的height
y.height = max(height(y.left),height(y.right)) + 1; // 更新并维护受影响结点的height
return y; // 将y返回
}


/**
* @description: 左旋方法
*/
private Node rotateLeft (Node x) {
Node y = x.right; // 取得x的右儿子
x.right = y.left; // 将x右儿子的左儿子("拖油瓶"结点)链接到旋转后的x的右链接中
y.left = x; // 调转x和它右儿子的父子关系,使x成为它原右儿子的左子树
x.height = max(height(x.left),height(x.right)) + 1; // 更新并维护受影响结点的height
y.height = max(height(y.left),height(y.right)) + 1; // 更新并维护受影响结点的height
return y; // 将y返回
}
平衡化操作的四种情况










编写平衡化代码
/**
* @description: 获得平衡因子
*/
private int getBalance (Node x) {
if(x == null) return 0;
return height(x.left) - height(x.right);
}
/**
* @description: 平衡化操作: 检测当前结点是否失衡,若失衡则进行平衡化
*/
private Node reBalance (Node x) {
int balanceFactor = getBalance(x);
if(balanceFactor > 1&&getBalance(x.left)>0) { // LL型,进行单次右旋
return rotateRight(x);
}
if(balanceFactor > 1&&getBalance(x.left)<=0) { //LR型 先左旋再右旋
Node t = rotateLeft(x);
return rotateRight(t);
}
if(balanceFactor < -1&&getBalance(x.right)<=0) {//RR型, 进行单次左旋
return rotateLeft(x);
}
if(balanceFactor < -1&&getBalance(x.right)>0) {// RL型,先右旋再左旋
Node t = rotateRight(x);
return rotateLeft(t);
}
return x;
}
AVL类的API编码
插入方法
/**
* @description: 插入结点(键值对)
*/
public Node put (Node x, int key, int val) {
if(x == null) return new Node(key, val); // 插入键值对
if (key<x.key) x.left = put(x.left, key, val); // 向左子树递归插入
else if(key>x.key) x.right = put(x.right,key, val); // 向右子树递归插入
else x.val = val; // key已存在, 替换val
x.height = max(height(x.left),height(x.right)) + 1; // 沿递归路径从下至上更新结点height属性
x = reBalance(x); // 沿递归路径从下往上, 检测当前结点是否失衡,若失衡则进行平衡化
return x;
}
删除方法
/**
* @description: 返回最小键
*/
private Node min (Node x) {
if(x.left == null) return x; // 如果左儿子为空,则当前结点键为最小值,返回
return min(x.left); // 如果左儿子不为空,则继续向左递归
}
public int min () {
if(root == null) return -1;
return min(root).key;
}
/**
* @description: 删除最小键的结点
*/
public Node deleteMin (Node x) {
if(x.left==null) return x.right; // 如果当前结点左儿子空,则将右儿子返回给上一层递归的x.left
x.left = deleteMin(x.left);// 向左子树递归, 同时重置搜索路径上每个父结点指向左儿子的链接
return x; // 当前结点不是min
}
public void deleteMin () {
root = deleteMin(root);
}
/**
* @description: 删除给定key的键值对
*/
private Node delete (int key,Node x) {
if(x == null) return null;
if (key<x.key) x.left = delete(key,x.left); // 向左子树查找键为key的结点
else if (key>x.key) x.right = delete(key,x.right); // 向右子树查找键为key的结点
else{
// 结点已经被找到,就是当前的x
if(x.left==null) return x.right; // 如果左子树为空,则将右子树赋给父节点的链接
if(x.right==null) return x.left; // 如果右子树为空,则将左子树赋给父节点的链接
Node inherit = min(x.right); // 取得结点x的继承结点
inherit.right = deleteMin(x.right); // 将继承结点从原来位置删除,并重置继承结点右链接
inherit.left = x.left; // 重置继承结点左链接
x = inherit; // 将x替换为继承结点
}
if(root == null) return root;
x.height = max(height(x.left),height(x.right)) + 1; // 沿递归路径从下至上更新结点height属性
x = reBalance(x); // 沿递归路径从下往上, 检测当前结点是否失衡,若失衡则进行平衡化
return x;
}
public void delete (int key) {
root = delete(key, root);
}
测试AVL和BST的动态操作对二叉树结构的影响
/**
* @description: 二叉树层序遍历
*/
private void levelIterator () {
LinkedList <Node> queue = new LinkedList <Node>();
Node current = null;
int childSize = 0;
int parentSize = 1;
queue.offer(root);
while(!queue.isEmpty()) {
current = queue.poll();//出队队头元素并访问
System.out.print(current.val +" ");
if(current.left != null)//如果当前节点的左节点不为空入队
{
queue.offer(current.left);
childSize++;
}
if(current.right != null)//如果当前节点的右节点不为空,把右节点入队
{
queue.offer(current.right);
childSize++;
}
parentSize--;
if (parentSize == 0)
{
parentSize = childSize;
childSize = 0;
System.out.println("");
}
}
}
public static void main(String [] args) {
BST bst = new BST();
bst.put(1,11);
bst.put(2,22);
bst.put(3,33);
bst.put(4,44);
bst.put(5,55);
bst.put(6,66);
bst.levelIterator();
}
11
22
33
44
55
66
public static void main (String [] args) {
AVL avl = new AVL();
avl.put(1,11);
avl.put(2,22);
avl.put(3,33);
avl.put(4,44);
avl.put(5,55);
avl.put(6,66);
avl.levelIterator();
}
44
22 55
11 33 66
全部代码
import java.util.LinkedList;
/**
* @Author: HuWan Peng
* @Date Created in 10:35 2017/12/29
*/
public class AVL {
Node root; // 根结点
private class Node {
int key,val;
Node left,right;
int height = 1; // 每个结点的高度属性
public Node (int key, int val) {
this.key = key;
this.val = val;
}
}
/**
* @description: 返回两个数中的最大值
*/
private int max (int a, int b) {
return a>b ? a : b;
}
/**
* @description: 获得当前结点的高度
*/
private int height (Node x) {
if(x == null) return 0;
return x.height;
}
/**
* @description: 获得平衡因子
*/
private int getBalance (Node x) {
if(x == null) return 0;
return height(x.left) - height(x.right);
}
/**
* @description: 右旋方法
*/
private Node rotateRight (Node x) {
Node y = x.left; // 取得x的左儿子
x.left = y.right; // 将x左儿子的右儿子("拖油瓶"结点)链接到旋转后的x的左链接中
y.right = x; // 调转x和它左儿子的父子关系,使x成为它原左儿子的右子树
x.height = max(height(x.left),height(x.right)) + 1; // 更新并维护受影响结点
y.height = max(height(y.left),height(y.right)) + 1; // 更新并维护受影响结点
return y; // 将y返回
}
/**
* @description: 左旋方法
*/
private Node rotateLeft (Node x) {
Node y = x.right; // 取得x的右儿子
x.right = y.left; // 将x右儿子的左儿子("拖油瓶"结点)链接到旋转后的x的右链接中
y.left = x; // 调转x和它右儿子的父子关系,使x成为它原右儿子的左子树
x.height = max(height(x.left),height(x.right)) + 1; // 更新并维护受影响结点
y.height = max(height(y.left),height(y.right)) + 1; // 更新并维护受影响结点
return y; // 将y返回
}
/**
* @description: 平衡化操作
*/
private Node reBalance (Node x) {
int balanceFactor = getBalance(x);
if(balanceFactor > 1&&getBalance(x.left)>0) { // LL型,进行单次右旋
return rotateRight(x);
}
if(balanceFactor > 1&&getBalance(x.left)<=0) { //LR型 先左旋再右旋
Node t = rotateLeft(x);
return rotateRight(t);
}
if(balanceFactor < -1&&getBalance(x.right)<=0) {//RR型, 进行单次左旋
return rotateLeft(x);
}
if(balanceFactor < -1&&getBalance(x.right)>0) {// RL型,先右旋再左旋
Node t = rotateRight(x);
return rotateLeft(t);
}
return x;
}
/**
* @description: 插入结点(键值对)
*/
public Node put (Node x, int key, int val) {
if(x == null) return new Node(key, val); // 插入键值对
if (key<x.key) x.left = put(x.left, key, val); // 向左子树递归插入
else if(key>x.key) x.right = put(x.right,key, val); // 向右子树递归插入
else x.val = val; // key已存在, 替换val
x.height = max(height(x.left),height(x.right)) + 1; // 沿递归路径从下至上更新结点height属性
x = reBalance(x); // 沿递归路径从下往上, 检测当前结点是否失衡,若失衡则进行平衡化
return x;
}
public void put (int key,int val) {
root = put(root,key,val);
}
/**
* @description: 返回最小键
*/
private Node min (Node x) {
if(x.left == null) return x; // 如果左儿子为空,则当前结点键为最小值,返回
return min(x.left); // 如果左儿子不为空,则继续向左递归
}
public int min () {
if(root == null) return -1;
return min(root).key;
}
/**
* @description: 删除最小键的结点
*/
public Node deleteMin (Node x) {
if(x.left==null) return x.right; // 如果当前结点左儿子空,则将右儿子返回给上一层递归的x.left
x.left = deleteMin(x.left);// 向左子树递归, 同时重置搜索路径上每个父结点指向左儿子的链接
return x; // 当前结点不是min
}
public void deleteMin () {
root = deleteMin(root);
}
/**
* @description: 删除给定key的键值对
*/
private Node delete (int key,Node x) {
if(x == null) return null;
if (key<x.key) x.left = delete(key,x.left); // 向左子树查找键为key的结点
else if (key>x.key) x.right = delete(key,x.right); // 向右子树查找键为key的结点
else{
// 结点已经被找到,就是当前的x
if(x.left==null) return x.right; // 如果左子树为空,则将右子树赋给父节点的链接
if(x.right==null) return x.left; // 如果右子树为空,则将左子树赋给父节点的链接
Node inherit = min(x.right); // 取得结点x的继承结点
inherit.right = deleteMin(x.right); // 将继承结点从原来位置删除,并重置继承结点右链接
inherit.left = x.left; // 重置继承结点左链接
x = inherit; // 将x替换为继承结点
}
if(root == null) return root;
x.height = max(height(x.left),height(x.right)) + 1; // 沿递归路径从下至上更新结点height属性
x = reBalance(x); // 沿递归路径从下往上, 检测当前结点是否失衡,若失衡则进行平衡化
return x;
}
public void delete (int key) {
root = delete(key, root);
}
private void levelIterator () {
LinkedList <Node> queue = new LinkedList <Node>();
Node current = null;
int childSize = 0;
int parentSize = 1;
queue.offer(root);
while(!queue.isEmpty()) {
current = queue.poll();//出队队头元素并访问
System.out.print(current.val +"-->");
if(current.left != null)//如果当前节点的左节点不为空入队
{
queue.offer(current.left);
childSize++;
}
if(current.right != null)//如果当前节点的右节点不为空,把右节点入队
{
queue.offer(current.right);
childSize++;
}
parentSize--;
if (parentSize == 0)
{
parentSize = childSize;
childSize = 0;
System.out.println("");
}
}
}
public static void main (String [] args) {
AVL avl = new AVL();
avl.put(1,11);
avl.put(2,22);
avl.put(3,33);
avl.put(4,44);
avl.put(5,55);
avl.put(6,66);
avl.levelIterator();
}
}
【算法】论平衡二叉树(AVL)的正确种植方法的更多相关文章
- 数据结构与算法--从平衡二叉树(AVL)到红黑树
数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树.算法的性能取决于树的形状,而树的形状取决于插入键的顺序.在最好的情况下,n个结点的树是完全平衡的,如下图"最好情况&q ...
- 平衡二叉树AVL - 插入节点后旋转方法分析
平衡二叉树 AVL( 发明者为Adel'son-Vel'skii 和 Landis)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1. 首先我们知道,当插入一个节点,从此插入点到树根 ...
- C平衡二叉树(AVL)创建和删除
AVL是最先发明的自平衡二叉查找树算法.在AVL中任何节点的两个儿子子树的高度最大差别为一,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n.查找.插入和删除在平均和最坏情况下 ...
- Java 树结构实际应用 四(平衡二叉树/AVL树)
平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在. 左边 BST 存在的问题分析: ...
- 趣味算法:字符串反转的N种方法(转)
老赵在反对北大青鸟的随笔中提到了数组反转.这的确是一道非常基础的算法题,然而也是一道很不平常的算法题(也许所有的算法深究下去都会很不平常).因为我写着写着,就写出来8种方法……现在我们以字符串的反转为 ...
- thinkphp3.2 cli模式的正确使用方法
最近要使用thinkphp3.2版本的cli模式,手动执的话没有问题,比如php /www/index.php home/article/get 这样没有问题,但是一般用cli模式都是定时任务比较多, ...
- Linux重启inotify配置max_user_watches无效被恢复默认值8192的正确修改方法
Linux下Rsync+inotify-tools实现数据实时同步中有一个重要的配置就是设置Inotify的max_user_watches值,如果不设置,当遇到大量文件的时候就会出现出错的情况. 一 ...
- MyEclipse10的正确破解方法
无法转载,故给出原文链接,以供需要者. MyEclipse10的正确破解方法
- 海量数据挖掘MMDS week2: 频繁项集挖掘 Apriori算法的改进:基于hash的方法
http://blog.csdn.net/pipisorry/article/details/48901217 海量数据挖掘Mining Massive Datasets(MMDs) -Jure Le ...
随机推荐
- C++雾中风景3:const用法的小结
const作为C与C++共有的关键字,很多使用的方式大同小异.但由于C++是一门面向对象的语言,在类和对象中有更多的使用规则.之前学习C语言的时候就被const这个关键字搅得焦头烂额,正巧也借这篇文章 ...
- [最短路]P1339 [USACO09OCT]热浪Heat Wave
题目描述 The good folks in Texas are having a heatwave this summer. Their Texas Longhorn cows make for g ...
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- 《java.util.concurrent 包源码阅读》21 CyclicBarrier和CountDownLatch
CyclicBarrier是一个用于线程同步的辅助类,它允许一组线程等待彼此,直到所有线程都到达集合点,然后执行某个设定的任务. 现实中有个很好的例子来形容:几个人约定了某个地方集中,然后一起出发去旅 ...
- window.location.href跳转至空白页
现象:window.location.href = "XXX"调到了空白页,但是将XXX在窗口地址栏输入就会可以访问到. 原因:就是XXX前缀没有加上"http://&q ...
- 面向对象编程 —— java实现函数求导
文章目录 ★引子 ★求导 ★最初的想法 ★初步的想法 ★后来的想法 ★最后的想法 ★编程范式 ★结尾 首先声明一点,本文主要介绍的是面向对象(OO)的思想,顺便谈下函数式编程,而不是教你如何准确地.科 ...
- Shell编程基础篇-下
1.1 条件表达式 1.1.1 文件判断 常用文件测试操作符 常用文件测试操作符 说明 -d文件,d的全拼为directory 文件存在且为目录则为真,即测试表达式成立 -f文件,f的全拼为file ...
- php综合运用技术
五.PHP综合应用 1.写出下列服务的用途和默认端口(新浪网技术部) ftp.ssh.http.telnet.https ftp:File Transfer Protocol,文件传输协议,是应用层的 ...
- 关于Switch case条件语句中无break的用法
关于Switch case条件语句的另类用法 今天在拜读一位前辈的程序时,遇到了这样一段程序: /***************************/ switch(operation ...
- linux apt-cache使用方法
apt-cache是linux下的一个apt软件包管理工具,它可查询apt的二进制软件包缓存文件.APT包管理的大多数信息查询功能都可以由apt-cache命令实现,通过apt-cache命令配合不同 ...