【算法】论平衡二叉树(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 ...
随机推荐
- CSS实现模糊效果
HTML代码如下: <body> <h1>body设置了模糊效果</h1> <div id="aa"></div> &l ...
- 关于事件mouseover ,mouseout ,mouseenter,mouseleave的区别
轮播中大多会选择mouseover和mouseout 这个时候是没有任何问题的 但当遇到有css3动画的时候,会发现移入移出过快 动画还没加载完成就需要执行下一个动画,完了动画样式就错乱了. 这时候 ...
- Cracking the Coding Interview 题目分析笔记—— Array and String
1.Determine if a string has all unique characters learn: 为了减少空间利用率,其比较优秀的算法一般都适用位操作 返回值的命名方法,我们需要学习 ...
- GIT常用命令(图片版)
Git 是一个很强大的分布式版本控制系统.它不但适用于管理大型开源软件的源代码,管理私人的文档和源代码也有很多优势. 本来想着只把最有用.最常用的 Git 命令记下来,但是总觉得这个也挺有用.那个也用 ...
- HTTP 2 新特性
HTTP 2 新特性 HTTP/2 不是彻底的重写http协议,HTTP methods,status codes 和 语义化都是相同的,并且它应该使用和HTTP/1.x 相同的api 表示协议. H ...
- 51Nod 1284 2 3 5 7的倍数 容斥原理
1284 2 3 5 7的倍数基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 收藏 关注给出一个数N,求1至N中,有多少个数不是2 3 5 7的倍数. 例如N = 1 ...
- 项目实战11—企业级nosql数据库应用与实战-redis的主从和集群
企业级nosql数据库应用与实战-redis 环境背景:随着互联网2.0时代的发展,越来越多的公司更加注重用户体验和互动,这些公司的平台上会出现越来越多方便用户操作和选择的新功能,如优惠券发放.抢红包 ...
- CentOS7操作系统参数优化
生产环境配置需要标准化,将常用操作写成脚本用于操作系统的初始化. #!/bin/bash #Date:2017 #This Script is for centos7.3 init #01.配置yum ...
- Ajax异步交互 [异步对象连接服务器]
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>X ...
- 将项目打包成jar,如何又将jar还原成项目
一.将项目打包成jar 第一步: 选择项目,鼠标右键,选择export ,出现如下 接下来就是点击Next,Next,最后点击Finish 后 会生成jar 二.将jar还原成项目 第一步 用反编译工 ...