• 平衡二叉树详解——PHP代码实现


    一、什么是平衡二叉树

    平衡二叉树(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代码实现

    平衡二叉树结点类

     1 <?php
     2 /**
     3  * AVLNode.php
     4  * Created on 2019/4/27 16:44
     5  * Created by Wilin
     6  */
     7 
     8 class AVLNode
     9 {
    10     public $data;
    11     public $left = null;
    12     public $right = null;
    13     public $bf = 0;
    14     public $parent = null;
    15 
    16     public function __construct($data) {
    17         $this->data = $data;
    18     }
    19 }

     中序遍历

     1 <?php
     2 /**
     3  * Traverse.php 遍历
     4  * Created on 2019/4/27 11:10
     5  * Created by Wilin
     6  */
     7 function midOrderTraverse($tree) {
     8     if($tree == null) {
     9         return;
    10     }
    11 
    12     midOrderTraverse($tree->left);
    13     printf("%s
    ", $tree->data);
    14     midOrderTraverse($tree->right);
    15 }

    平衡二叉树

      1 <?php
      2 /**
      3  * AVLTree.php
      4  * Created on 2019/4/27 16:51
      5  * Created by Wilin
      6  */
      7 
      8 include "AVLNode.php";
      9 include "../Traverse.php";
     10 
     11 class AVLTree
     12 {
     13     private $root;
     14 
     15     const LH = 1;
     16     const EH = 0;
     17     const RH = -1;
     18 
     19     public function getTree() {
     20         return $this->root;
     21     }
     22 
     23     public function insert(int $data) {
     24         $this->insert_node($data, $this->root);
     25     }
     26 
     27     /**
     28      * 插入节点
     29      * @param int $data
     30      * @param $tree
     31      * @return bool 是否需要调整树结构,true:是,false:否
     32      */
     33     protected function insert_node(int $data, &$tree) {
     34 
     35         //创建节点
     36         if (!$tree) {
     37             $tree = new AVLNode($data);
     38             $tree->bf = self::EH;
     39             return true;                            //插入成功之后需要判断是否需要调整
     40         }
     41 
     42         if ($data < $tree->data) {
     43             //递归插入节点
     44             if (!$this->insert_node($data, $tree->left)) {
     45                 return false;
     46             } else {
     47                 //更正新插入节点对父节点的指向
     48                 if (empty($tree->left->parent)) {
     49                     $tree->left->parent = $tree;
     50                 }
     51                 //判断是否需要调整子树
     52                 switch ($tree->bf) {
     53                     case self::LH:                  //左子树偏高,需要对左子树进行调整
     54                         $this->left_balance($tree);
     55                         return false;               //已经进行过调整,不需要继续调整
     56                     case self::EH:
     57                         $tree->bf = self::LH;
     58                         return true;                //由等高变为左高,树的整体高度发生变化,需要继续判断上层节点是否需要调整
     59                     case self::RH:
     60                         $tree->bf = self::EH;
     61                         return false;               //由右高变为等高,树的整体高度没有发生变化,不需要调整
     62                 }
     63             }
     64         } else {
     65             if (!$this->insert_node($data,$tree->right)) {
     66                 return false;
     67             } else {
     68                 if (empty($tree->right->parent)) {
     69                     $tree->right->parent = $tree;
     70                 }
     71                 switch ($tree->bf) {
     72                     case self::LH:
     73                         $tree->bf = self::EH;
     74                         return false;
     75                     case self::EH:
     76                         $tree->bf = self::RH;
     77                         return true;
     78                     case self::RH:
     79                         $this->right_balance($tree);
     80                         return false;
     81                 }
     82             }
     83         }
     84     }
     85 
     86     /**
     87      * 右旋
     88      * @param $tree
     89      */
     90     protected function right_rotate(&$tree) {
     91         //修改父节点与子树之间的指向时需要特别注意根节点
     92 
     93         $subTree = $tree->left;
     94         //修改子树对父节点的指向
     95         if ($tree->parent) {
     96             $subTree->parent = $tree->parent;
     97             $left = false;              //调整之前记录当前调整的子树是父节点的左子树还是右子树
     98             if($tree->parent->left == $tree){
     99                 $left = true;
    100             }
    101         } else {
    102             $subTree->parent = null;    //根节点的父节点为空
    103         }
    104         //交换节点位置
    105         $tree->left = $subTree->right;
    106         $tree->parent = $subTree;
    107         $subTree->right = $tree;
    108 
    109         $tree = $subTree;
    110         //修改父节点对子树的指向
    111         if (!$tree->parent) {
    112             $this->root = $tree;
    113         } else {
    114             if ($left) {
    115                 $tree->parent->left = $tree;
    116             } else {
    117                 $tree->parent->right = $tree;
    118             }
    119         }
    120     }
    121 
    122     /**
    123      * 左旋
    124      * @param $tree
    125      */
    126     protected function left_rotate(&$tree) {
    127 
    128         $subTree = $tree->right;
    129         if ($tree->parent) {
    130             $subTree->parent  = $tree->parent;
    131             $left = true;
    132             if ($tree->parent->right == $tree) {
    133                 $left = false;
    134             }
    135         } else {
    136             $subTree->parent = null;
    137         }
    138 
    139         $tree->right = $subTree->left;
    140         $tree->parent = $subTree;
    141         $subTree->left = $tree;
    142         $tree = $subTree;
    143         if (!$tree->parent) {
    144             $this->root = $tree;
    145         } else {
    146             if ($left) {
    147                 $tree->parent->left = $tree;
    148             } else {
    149                 $tree->parent->right = $tree;
    150             }
    151         }
    152     }
    153 
    154     /**
    155      * 调整左子树
    156      * @param $tree
    157      */
    158     protected function left_balance(&$tree) {
    159         $subTree = $tree->left;
    160         switch ($subTree->bf) {
    161             case self::LH:
    162                 $tree->bf = $subTree->bf = self::EH;            //先修改平衡因子,再进行旋转
    163                 $this->right_rotate($tree);
    164                 break;
    165             case self::RH:
    166                 $subTree_r = $subTree->right;
    167                 switch ($subTree_r->bf) {
    168                     case self::LH:
    169                         $tree->bf = self::RH;
    170                         $subTree->bf = self::EH;
    171                         break;
    172                     case self::RH:
    173                         $tree->bf = self::EH;
    174                         $subTree->bf = self::LH;
    175                         break;
    176                 }
    177                 $subTree_r->bf = self::EH;
    178                 $this->left_rotate($subTree);
    179                 $this->right_rotate($tree);
    180                 break;
    181         }
    182     }
    183 
    184     /**
    185      * 调整右子树
    186      * @param $tree
    187      */
    188     protected function right_balance(&$tree) {
    189         $subTree = $tree->right;
    190         switch ($subTree->bf) {
    191             case self::RH:
    192                 $tree->bf = $subTree->bf = self::EH;
    193                 $this->left_rotate($tree);
    194                 break;
    195             case self::LH:
    196                 $subTree_l = $subTree->left;
    197                 switch ($subTree_l->bf) {
    198                     case self::RH:
    199                         $tree->bf = self::LH;
    200                         $subTree->bf = self::EH;
    201                         break;
    202                     case self::EH:
    203                         $tree->bf = $subTree->bf = self::EH;
    204                         break;
    205                     case self::LH:
    206                         $tree->bf = self::EH;
    207                         $subTree->bf = self::RH;
    208                         break;
    209                 }
    210                 $subTree_l->bf = self::EH;
    211                 $this->right_rotate($subTree);
    212                 $this->left_rotate($tree);
    213         }
    214     }
    215 }
    216 
    217 $avlTree = new AVLTree();
    218 $avlTree->insert(3);
    219 $avlTree->insert(2);
    220 $avlTree->insert(1);
    221 $avlTree->insert(4);
    222 $avlTree->insert(5);
    223 $avlTree->insert(6);
    224 $avlTree->insert(7);
    225 $avlTree->insert(10);
    226 $avlTree->insert(9);
    227 $avlTree->insert(8);
    228 midOrderTraverse($avlTree->getTree());
    229 print_r($avlTree->getTree());

    打印结果如下

    E:www	ree2>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

  • 相关阅读:
    javascript 判断电话号码的格式
    JavaScript 'Pig latin is cool'==>'igPay atinlay siay oolcay'
    JavaScript 找出特殊数字如135 = 1^1 + 3^2 + 5^3
    nginx+Apache实现动静分离
    MYSQL数据库的主从复制
    Connection could not be established with host smtp.163.com [Connection timed out #110]
    Yii2 的 redis 应用
    Yii2 模块名、控制器名、方法名
    讯搜
    支付宝异步通知时间点分布
  • 原文地址:https://www.cnblogs.com/weiyalin/p/10817111.html
Copyright © 2020-2023  润新知