【算法】论平衡二叉树(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 ...
随机推荐
- eclipse自定义代码模板
eclipse自定义代码模板 Eclipse 提供了非常多的代码模板,我们可以通过 Windows->Preferences->Java->Editor->Templates ...
- javascript常用的Math对象的方法
简介 Math对象是在程序编程中用于执行一些数学任务的.Math 对象并不像 Date 和 String 那样是对象的类,因此没有构造函数 Math(),像 Math.sin() 这样的函数只是函数, ...
- 罗培羽—C语言简单游戏编程教学
编写许多软件都需要有菜单,那么如果我们使用tc之类的软件来编译程序的话,我们该怎么编写菜单呢?让我们一起来试试吧!第一步:简单例子 我们先来写个最简单的例子:#include<std ...
- Beautifulsoup分解
from urllib.request import Request, ProxyHandler from urllib.request import build_opener from bs4 im ...
- python基础0
1.运行:D:\tools\python\python-2.7.10.amd64=>安装到c:\python 2.环境变量:path:c:\Python27 3.cmd:python回车 //s ...
- JSP8
一.EL表达式 JSP表达式语言(EL)使得访问存储在JavaBean中的数据变得非常简单.JSP EL既可以用来创建算术表达式也可以用来创建逻辑表达式.在JSP EL表达式内可以使用整型数,浮点数 ...
- Caddy服务器搭建和实现文件共享
1:Caddy介绍 作为新兴 Web 服务器,Caddy 提供了很多简单易用的功能而没有历史的包袱,其默认支持并且能帮你自动配置 HTTP/2.HTTPS,对于 IPV6.WebSockets 都有很 ...
- MySQL服务找不到了,navicat打不开数据库连接
今天打开Navicat看看连接名,突然发现连接不上了,打开服务发现MySQL服务不见了,所以手动安装了遍MySQL服务. 详细步骤如下: 1.管理员身份打开cmd,切换到MySQL安装目录下的bin目 ...
- php加密解密处理类
[PHP]代码 <?php /*=========================================================== = 版权协议: = GPL (The GN ...
- 2017最新PHP面试题
这几天在面试,下面分享一下这几天面试所遇到的笔试题,目前还不打算工作,面试题会持续更新的,有些不想写答案了,有心的可以自己看着面试题查一下感觉记忆更深点.下面分享一下这几天遇到的php面试题. 掌贝面 ...