一、什么是平衡二叉树

平衡二叉树(Self-Balancing Binary Search Tree 或者 Height-Balancing Binary Search Tree)译为 自平衡的二叉查找树或者高度平衡的二叉查找树,简称平衡二叉树,也叫 AVL 树,是一种二叉排序树。每个节点的左子树和右子树的高度差至多等于 1,我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子 BF(Balance Factor),那么平衡二叉树上所有结点的平衡因子只可能是  -1,0,1。只要树上有结点的平衡因子的绝对值大于 1,则该二叉树就是不平衡的。

下面举四个例子:

  • 图1不满足平衡二叉树定义,58和88这两个结点的平衡因子BF分别是2和-2,不是平衡二叉树
  • 图2不是平衡二叉树,因为平衡二叉树首要要是二叉排序树,59比58大却是58的左子树,这是不符合二叉排序树的定义的
  • 图3不满足平衡因子小于等于1的要求,对58这个节点来说,平衡因子BF的值是3,因而不是平衡二叉树
  • 图4满足平衡二叉树的定义,是平衡二叉树

二、平衡二叉树的实现原理

平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转(左旋或右旋),使之成为新的平衡子树。 

最小不平衡子树

距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树。

如下图,当新插入结点37时,距离它最近的平衡因子绝对值超过1的结点是58 (即它的左子树高度2减去右子树高度0),所以从58开始以下的子树为最小不平衡子树。

左旋/右旋

  • 当右子树比左子树高,即平衡因子小于-1,需要进行左旋,如下图

  • 当右子树比左子树低,即平衡因子大于1,需要进行右旋,如下图

实例

假设插入节点的顺序是{3,2,1,4,5,6,7,10,9,8}

根据二叉排序树的特性,我们通常会将它构建成如下图1的样子。虽然它完全符合二叉排序树的定义,但是对这样高度达到8的二叉树查找是非常不利的。我们更期望能构建成下图2的样子,这样才能提供高效的查询效率。

下面就开始构建上图2

对于{3,2,1,4,5,6,7,10,9,8}的前两位3和2,我们正常的构建,到了第三个数1时发现根节点的平衡因子变成了2,需要把以 3 为根节点的子树进行右旋

插入第四个节点 4 的时候,左右子树高度为 -1,符合平衡二叉树要求,继续插入第五个节点,此时又不符合平衡二叉树的要求了,这个时候右子树比较高,需要左旋:旋转的时候以最小不平衡子树为单位,此时最小的不平衡子树是3、4、5节点构成的子树,我们以4为中心进行左旋

继续增加节点,当插入节点 6 时,发现根节点 2 上维护的高度差值为 -2,又不满足平衡二叉树了,这个时候,需要以 2 为中心对树进行左旋:如下图所示(右子树中旋转到根节点的节点对应子树需要移到旋转后二叉树的左子树中):

增加结点7,同样的左旋,使得整棵树达到平衡

继续增加节点 10,结构无变化。再插入节点 9,发现结点7的BF变成-2又需要调整。但是这次调整需要绕个弯,不能简单的进行简单的左旋,需要先将以10作为根节点的子树做一次右转,再将以7为根节点的子树做一次左转,让这棵不平衡子树转化为平衡子树

最后,插入节点8,此时情况和刚才类似,这个时候,我们以 9 为根节点对子树进行右旋,再以6为根节点对子树进行左旋,最终达到平衡状态

相信大家应该有点明白,所谓的平衡二叉树,其实就是在二叉排序树创建过程中保证它的平衡性,一旦发现有不平衡的情况,马上处理,这样就不会造成不可收拾的情况出现。

通过刚才这个例子,你会发现,当最小不平衡子树根结点的平衡因子BF是大于1时,就右旋,小于-1时就左旋

三、平衡二叉树PHP代码实现

平衡二叉树结点类

 <?php
/**
* AVLNode.php
* Created on 2019/4/27 16:44
* Created by Wilin
*/ class AVLNode
{
public $data;
public $left = null;
public $right = null;
public $bf = 0;
public $parent = null; public function __construct($data) {
$this->data = $data;
}
}

中序遍历

 <?php
/**
* Traverse.php 遍历
* Created on 2019/4/27 11:10
* Created by Wilin
*/
function midOrderTraverse($tree) {
if($tree == null) {
return;
} midOrderTraverse($tree->left);
printf("%s\n", $tree->data);
midOrderTraverse($tree->right);
}

平衡二叉树

 <?php
/**
* AVLTree.php
* Created on 2019/4/27 16:51
* Created by Wilin
*/ include "AVLNode.php";
include "../Traverse.php"; class AVLTree
{
private $root; const LH = 1;
const EH = 0;
const RH = -1; public function getTree() {
return $this->root;
} public function insert(int $data) {
$this->insert_node($data, $this->root);
} /**
* 插入节点
* @param int $data
* @param $tree
* @return bool 是否需要调整树结构,true:是,false:否
*/
protected function insert_node(int $data, &$tree) { //创建节点
if (!$tree) {
$tree = new AVLNode($data);
$tree->bf = self::EH;
return true; //插入成功之后需要判断是否需要调整
} if ($data < $tree->data) {
//递归插入节点
if (!$this->insert_node($data, $tree->left)) {
return false;
} else {
//更正新插入节点对父节点的指向
if (empty($tree->left->parent)) {
$tree->left->parent = $tree;
}
//判断是否需要调整子树
switch ($tree->bf) {
case self::LH: //左子树偏高,需要对左子树进行调整
$this->left_balance($tree);
return false; //已经进行过调整,不需要继续调整
case self::EH:
$tree->bf = self::LH;
return true; //由等高变为左高,树的整体高度发生变化,需要继续判断上层节点是否需要调整
case self::RH:
$tree->bf = self::EH;
return false; //由右高变为等高,树的整体高度没有发生变化,不需要调整
}
}
} else {
if (!$this->insert_node($data,$tree->right)) {
return false;
} else {
if (empty($tree->right->parent)) {
$tree->right->parent = $tree;
}
switch ($tree->bf) {
case self::LH:
$tree->bf = self::EH;
return false;
case self::EH:
$tree->bf = self::RH;
return true;
case self::RH:
$this->right_balance($tree);
return false;
}
}
}
} /**
* 右旋
* @param $tree
*/
protected function right_rotate(&$tree) {
//修改父节点与子树之间的指向时需要特别注意根节点 $subTree = $tree->left;
//修改子树对父节点的指向
if ($tree->parent) {
$subTree->parent = $tree->parent;
$left = false; //调整之前记录当前调整的子树是父节点的左子树还是右子树
if($tree->parent->left == $tree){
$left = true;
}
} else {
$subTree->parent = null; //根节点的父节点为空
}
//交换节点位置
$tree->left = $subTree->right;
$tree->parent = $subTree;
$subTree->right = $tree; $tree = $subTree;
//修改父节点对子树的指向
if (!$tree->parent) {
$this->root = $tree;
} else {
if ($left) {
$tree->parent->left = $tree;
} else {
$tree->parent->right = $tree;
}
}
} /**
* 左旋
* @param $tree
*/
protected function left_rotate(&$tree) { $subTree = $tree->right;
if ($tree->parent) {
$subTree->parent = $tree->parent;
$left = true;
if ($tree->parent->right == $tree) {
$left = false;
}
} else {
$subTree->parent = null;
} $tree->right = $subTree->left;
$tree->parent = $subTree;
$subTree->left = $tree;
$tree = $subTree;
if (!$tree->parent) {
$this->root = $tree;
} else {
if ($left) {
$tree->parent->left = $tree;
} else {
$tree->parent->right = $tree;
}
}
} /**
* 调整左子树
* @param $tree
*/
protected function left_balance(&$tree) {
$subTree = $tree->left;
switch ($subTree->bf) {
case self::LH:
$tree->bf = $subTree->bf = self::EH; //先修改平衡因子,再进行旋转
$this->right_rotate($tree);
break;
case self::RH:
$subTree_r = $subTree->right;
switch ($subTree_r->bf) {
case self::LH:
$tree->bf = self::RH;
$subTree->bf = self::EH;
break;
case self::RH:
$tree->bf = self::EH;
$subTree->bf = self::LH;
break;
}
$subTree_r->bf = self::EH;
$this->left_rotate($subTree);
$this->right_rotate($tree);
break;
}
} /**
* 调整右子树
* @param $tree
*/
protected function right_balance(&$tree) {
$subTree = $tree->right;
switch ($subTree->bf) {
case self::RH:
$tree->bf = $subTree->bf = self::EH;
$this->left_rotate($tree);
break;
case self::LH:
$subTree_l = $subTree->left;
switch ($subTree_l->bf) {
case self::RH:
$tree->bf = self::LH;
$subTree->bf = self::EH;
break;
case self::EH:
$tree->bf = $subTree->bf = self::EH;
break;
case self::LH:
$tree->bf = self::EH;
$subTree->bf = self::RH;
break;
}
$subTree_l->bf = self::EH;
$this->right_rotate($subTree);
$this->left_rotate($tree);
}
}
} $avlTree = new AVLTree();
$avlTree->insert(3);
$avlTree->insert(2);
$avlTree->insert(1);
$avlTree->insert(4);
$avlTree->insert(5);
$avlTree->insert(6);
$avlTree->insert(7);
$avlTree->insert(10);
$avlTree->insert(9);
$avlTree->insert(8);
midOrderTraverse($avlTree->getTree());
print_r($avlTree->getTree());

打印结果如下

E:\www\tree\2>php AVLTree.php
1
2
3
4
5
6
7
8
9
10
AVLNode Object
(
[data] => 4
[left] => AVLNode Object
(
[data] => 2
[left] => AVLNode Object
(
[data] => 1
[left] =>
[right] =>
[bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [right] => AVLNode Object
(
[data] => 3
[left] =>
[right] =>
[bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [right] => AVLNode Object
(
[data] => 7
[left] => AVLNode Object
(
[data] => 6
[left] => AVLNode Object
(
[data] => 5
[left] =>
[right] =>
[bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [right] =>
[bf] => 1
[parent] => AVLNode Object
*RECURSION*
) [right] => AVLNode Object
(
[data] => 9
[left] => AVLNode Object
(
[data] => 8
[left] =>
[right] =>
[bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [right] => AVLNode Object
(
[data] => 10
[left] =>
[right] =>
[bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [bf] => 0
[parent] => AVLNode Object
*RECURSION*
) [bf] => -1
[parent] =>
)

参考书籍:《大话数据结构》

其他参考:https://articles.zsxq.com/id_dgm8kpxzw4xo.html

平衡二叉树详解——PHP代码实现的更多相关文章

  1. Python - 元组(tuple) 详解 及 代码

    元组(tuple) 详解 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/17290967 元组是存放任意元素集合,不能修 ...

  2. Python - 字典(dict) 详解 及 代码

    字典(dict) 详解 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/17291329 字典(dict)是表示映射的数据 ...

  3. 深度学习之卷积神经网络(CNN)详解与代码实现(一)

    卷积神经网络(CNN)详解与代码实现 本文系作者原创,转载请注明出处:https://www.cnblogs.com/further-further-further/p/10430073.html 目 ...

  4. C#的String.Split 分割字符串用法详解的代码

    代码期间,把代码过程经常用的内容做个珍藏,下边代码是关于C#的String.Split 分割字符串用法详解的代码,应该对码农们有些用途. 1) public string[] Split(params ...

  5. laravel 框架配置404等异常页面的方法详解(代码示例)

    本篇文章给大家带来的内容是关于laravel 框架配置404等异常页面的方法详解(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 在Laravel中所有的异常都由Handl ...

  6. Android java程序员必备技能,集合与数组中遍历元素,增强for循环的使用详解及代码

    Android java程序员必备技能,集合与数组中遍历元素, 增强for循环的使用详解及代码 作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 For ...

  7. UIWebView用法详解及代码分享

    今天我们来详细UIWebView用法.UIWebView是iOS内置的浏览器控件,可以浏览网页.打开文档等 能够加载html/htm.pdf.docx.txt等格式的文件. 用UIWebView我们就 ...

  8. 【转载】 深度学习之卷积神经网络(CNN)详解与代码实现(一)

    原文地址: https://www.cnblogs.com/further-further-further/p/10430073.html ------------------------------ ...

  9. 设计模式相关面试问题-Builder基础详解与代码解读

    java的builder模式详解: 概念:建造者模式是较为复杂的创建型模式,它将客户端与多含多个组成部分(或部件)的复杂对象的创建过程分离. 使用场景:当构造一个对象需要很多参数的时候,并且参数的个数 ...

随机推荐

  1. (转载)golang 整数常量INT_MAX INT_MIN最大值最小值

    转载地址:https://blog.csdn.net/bdss58/article/details/78388858 在C语言中,有标准库limits.h定义了一些最大最小值常量,例如int类型的最大 ...

  2. Jmeter5.11安装

    jmeter5.11要对应jdk1.8以上版本 1.选择zip后缀进行下载 2.配置环境变量 (1)电脑桌面---->"计算机"图标---->鼠标右键选择"属 ...

  3. SQL Delta实用案例介绍

    概述 本篇文章主要介绍SQL DELTA的简单使用.为了能够更加明了的说明其功能,本文将通过实际项目中的案例加以介绍. 主要容 Ÿ   SQL DELTA 简介 Ÿ   创建SQL DELTA项目 Ÿ ...

  4. openstack错误问题定位及调试

  5. vue---定义全局变量或函数

    开发项目的时候,有很多的东西需要重复使用,例如函数或者变量等,例如网站服务器地址,token等,这时候就需要设置一波全局变量和全局函数 定义全局函数 原理 新建一个模块文件,然后在main.js里面通 ...

  6. (obj) error: LNK2019: 无法解析的外部符号解决方法

    利用VS2010 C++ 调用 labview生成的动态库编程时无法生成exe 报错信息: Cjj_32.obj : error LNK2019: 无法解析的外部符号 _GetPXIResource@ ...

  7. [图片问答]LODOP打印的行间距字间距

    LODOP可以打印纯文本,也可以是超文本,关于哪些打印项是纯文本,哪些打印项是超文本,之前有博文相关介绍:LODOP中的纯文本和超文本打印项. 之前的关于纯文本的行间距字间距介绍:Lodop设置文本项 ...

  8. 【环境搭建与软件安装】How to install CUDNN or uninstall

    前言 CuDnn是用于深度学习的GPU加速库,安装好NVIDIA和CUDA之后,安装CuDnn就简单多了,可参考官方文档. 操作过程 1. 下载cuDnn. 需要在NVIDIA官网注册账号,登录之后下 ...

  9. C# .NET 支付宝IOT小程序AES密钥解密

    实际测试 KEY 是 16个 byte,byte[] byteKEY = System.Convert.FromBase64String(key);     注意:是Convert.FromBase6 ...

  10. 【机器学习之二】python开发spark案例

    环境 spark-1.6 python3.5 一.wordcount # -*- coding:utf-8 -*- ''' Created on 2019年5月13日 @author: Adminis ...