一、什么是平衡二叉树

平衡二叉树(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. 微信小程序之网络通信

    关于网络通信,这里我使用的是wx.request,官方代码示例如下: wx.request({ url: 'test.php', //仅为示例,并非真实的接口地址 data: { x: '', y: ...

  2. 大数据 | 分布式文件系统HDFS 练习

    本次作业来自于:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3292 利用Shell命令与HDFS进行交互 以”./bin/dfs ...

  3. html5 css3 背景视频循环播放代码

    <div style ="position: absolute; z-index: -1; top: 0px; left: 0px; bottom: 0px; right: 0px; ...

  4. 对snapshot isolation和write-snapshot isolation的一些思考

    数据库中存在读异常和写异常. 所谓snapshot,目的在于保证事务执行的各个阶段,读相同的数据项得到的结果没有变化,这样一来就避免了不可重复读.幻读等读数据异常. 但是仅仅是读数据不变还不够,因为这 ...

  5. [Beta]第七次 Scrum Meeting

    [Beta]第七次 Scrum Meeting 写在前面 会议时间 会议时长 会议地点 2019/5/17 22:00 10min 大运村公寓6F寝室 附Github仓库:WEDO 例会照片 工作情况 ...

  6. Python实例100个(基于最新Python3.7版本)

    Python3 100例 原题地址:   http://www.runoob.com/python/python-100-examples.html    git地址:    https://gith ...

  7. Python中的args和kwargs

    有时,你会看到python中定义函数的时候带有两个奇怪的参数:*args.**kwargs.如果你曾经想知道它们是干什么的,或者想知道你的IDE为什么在main()函数中定义它们,那么本文可以帮助到你 ...

  8. Mysql中的Date转换

    一.背景 Mysql中有张表,表的一列为Date类型. 1. 插入日期xxx.setCreateTime(new Date())mybatis.insert(xxx) 2. 读取日期用Mybaitis ...

  9. Qt编写安防视频监控系统13-视频存储

    一.前言 一般视频监控行业都会选择把视频存储在本地NVR或者服务器上,而不是存储在客户端电脑,只有当用户经费预算有限的时候,或者用户特殊需求要求存储在本地客户端电脑的时候才会开启存储到本地,正常来说视 ...

  10. LinkedBlockingQueue与ArrayBlockingQueue

    阻塞队列与普通的队列(LinkedList/ArrayList)相比,支持在向队列中添加元素时,队列的长度已满阻塞当前添加线程,直到队列未满或者等待超时:从队列中获取元素时,队列中元素为空 ,会将获取 ...