PHP二叉树(二):平衡二叉树(AVL)

<?php
/**
 * author:zhongjin
 * time:2016/10/20 11:53
 * description: 平衡二叉树
 */
//结点
class Node
{
    public $key;
    public $parent;
    public $left;
    public $right;
    public $bf; //平衡因子
  
    public function __construct($key)
    {
        $this--->key = $key;
        $this->parent = NULL;
        $this->left = NULL;
        $this->right = NULL;
        $this->bf = 0;
    }
}
  
//平衡二叉树
class Avl
{
    public $root;
    const LH = +1;  //左高
    const EH = 0;   //等高
    const RH = -1;  //右高
  
    /**
     * 初始化树结构
     * @param $arr 初始化树结构的数组
     * @return null
     */
    public function init($arr)
    {
        $this->root = new Node($arr[0]);
        for ($i = 1; $i < count($arr); $i++) {
            $this->Insert($arr[$i]);
        }
    }
  
    /**
     * (对内)中序遍历
     * @param $root (树或子树的)根节点
     * @return null
     */
    private function mid_order($root)
    {
        if ($root != NULL) {
            $this->mid_order($root->left);
            echo $root->key . "-" . $root->bf . "  ";
            $this->mid_order($root->right);
        }
    }
  
    /**
     * (对外)中序遍历
     * @param null
     * @return null
     */
    public function MidOrder()
    {
        $this->mid_order($this->root);
    }
  
    /**
     * 将以$root为根节点的最小不平衡二叉树做右旋处理
     * @param $root(树或子树)根节点
     * @return null
     */
    private function R_Rotate($root)
    {
        $L = $root->left;
        if (!is_NULL($root->parent)) {
            $P = $root->parent;
            if ($root == $P->left) {
                $P->left = $L;
            } else {
                $P->right = $L;
            }
            $L->parent = $P;
        } else {
            $L->parent = NULL;
        }
        $root->parent = $L;
        $root->left = $L->right;
        $L->right = $root;
        //这句必须啊!
        if ($L->parent == NULL) {
            $this->root = $L;
        }
    }
  
    /**
     * 将以$root为根节点的最小不平衡二叉树做左旋处理
     * @param $root(树或子树)根节点
     * @return null
     */
    private function L_Rotate($root)
    {
        $R = $root->right;
        if (!is_NULL($root->parent)) {
            $P = $root->parent;
            if ($root == $P->left) {
                $P->left = $R;
            } else {
                $P->right = $R;
            }
            $R->parent = $P;
        } else {
            $R->parent = NULL;
        }
        $root->parent = $R;
        $root->right = $R->left;
        $R->left = $root;
        //这句必须啊!
        if ($R->parent == NULL) {
            $this->root = $R;
        }
    }
  
    /**
     * 对以$root所指结点为根节点的二叉树作左平衡处理
     * @param $root(树或子树)根节点
     * @return null
     */
    public function LeftBalance($root)
    {
        $L = $root->left;
        $L_bf = $L->bf;
        switch ($L_bf) {
            //检查root的左子树的平衡度,并作相应的平衡处理
            case self::LH:    //新结点插入在root的左孩子的左子树上,要做单右旋处理
                $root->bf = $L->bf = self::EH;
                $this->R_Rotate($root);
                break;
            case self::RH:    //新节点插入在root的左孩子的右子树上,要做双旋处理
                $L_r = $L->right;   //root左孩子的右子树根
                $L_r_bf = $L_r->bf;
                //修改root及其左孩子的平衡因子
                switch ($L_r_bf) {
                    case self::LH:
                        $root->bf = self::RH;
                        $L->bf = self::EH;
                        break;
                    case self::EH:
                        $root->bf = $L->bf = self::EH;
                        break;
                    case self::RH:
                        $root->bf = self::EH;
                        $L->bf = self::LH;
                        break;
                }
                $L_r->bf = self::EH;
                //对root的左子树作左平衡处理
                $this->L_Rotate($L);
                //对root作右平衡处理
                $this->R_Rotate($root);
        }
    }
  
    /**
     * 对以$root所指结点为根节点的二叉树作右平衡处理
     * @param $root(树或子树)根节点
     * @return null
     */
    public function RightBalance($root)
    {
        $R = $root->right;
        $R_bf = $R->bf;
        switch ($R_bf) {
            //检查root的右子树的平衡度,并作相应的平衡处理
            case self::RH:    //新结点插入在root的右孩子的右子树上,要做单左旋处理
                $root->bf = $R->bf = self::EH;
                $this->L_Rotate($root);
                break;
            case self::LH:    //新节点插入在root的右孩子的左子树上,要做双旋处理
                $R_l = $R->left;   //root右孩子的左子树根
                $R_l_bf = $R_l->bf;
                //修改root及其右孩子的平衡因子
                switch ($R_l_bf) {
                    case self::RH:
                        $root->bf = self::LH;
                        $R->bf = self::EH;
                        break;
                    case self::EH:
                        $root->bf = $R->bf = self::EH;
                        break;
                    case self::LH:
                        $root->bf = self::EH;
                        $R->bf = self::RH;
                        break;
                }
                $R_l->bf = self::EH;
                //对root的右子树作右平衡处理
                $this->R_Rotate($R);
                //对root作左平衡处理
                $this->L_Rotate($root);
        }
    }
  
    /**
     * 查找树中是否存在$key对应的节点
     * @param $key 待搜索数字
     * @return $key对应的节点
     */
    public function search($key)
    {
        $current = $this->root;
        while ($current != NULL) {
            if ($current->key == $key) {
                return $current;
            } elseif ($current->key > $key) {
                $current = $current->left;
            } else {
                $current = $current->right;
            }
        }
        return $current;
    }
  
    /**
     * 查找树中的最小关键字
     * @param $root 根节点
     * @return 最小关键字对应的节点
     */
    function search_min($root)
    {
        $current = $root;
        while ($current->left != NULL) {
            $current = $current->left;
        }
        return $current;
    }
  
    /**
     * 查找树中的最大关键字
     * @param $root 根节点
     * @return 最大关键字对应的节点
     */
    function search_max($root)
    {
        $current = $root;
        while ($current->right != NULL) {
            $current = $current->right;
        }
        return $current;
    }
  
    /**
     * 查找某个$key在中序遍历时的直接前驱节点
     * @param $x 待查找前驱节点的节点引用
     * @return 前驱节点引用
     */
    private function predecessor($x)
    {
        //左子节点存在,直接返回左子节点的最右子节点
        if ($x->left != NULL) {
            return $this->search_max($x->left);
        }
        //否则查找其父节点,直到当前结点位于父节点的右边
        $p = $x->parent;
        //如果x是p的左孩子,说明p是x的后继,我们需要找的是p是x的前驱
        while ($p != NULL && $x == $p->left) {
            $x = $p;
            $p = $p->parent;
        }
        return $p;
    }
  
    /**
     * 查找某个$key在中序遍历时的直接后继节点
     * @param $x 待查找后继节点的节点引用
     * @return 后继节点引用
     */
    private function successor($x)
    {
        if ($x->left != NULL) {
            return $this->search_min($x->right);
        }
        $p = $x->parent;
        while ($p != NULL && $x == $p->right) {
            $x = $p;
            $p = $p->parent;
        }
        return $p;
    }
  
    /**
     * (对内)插入结点,如果结点不存在则插入,失去平衡要做平衡处理
     * @param $root 根节点 $key 待插入树的数字
     * @return null
     */
    private function insert_node(&$root, $key)
    {
        //找到了插入的位置,插入新节点
        if (is_null($root)) {
            $root = new Node($key);
            //插入结点成功
            return TRUE;
        } else {
            //在树中已经存在和$key相等的结点
            if ($key == $root->key) {
                //插入节点失败
                return FALSE;
            } //在root的左子树中继续搜索
            elseif ($key < $root->key) {
                //插入左子树失败
                if (!($this->insert_node($root->left, $key))) {
                    //树未长高
                    return FALSE;
                }
  
                //成功插入,修改平衡因子
                if (is_null($root->left->parent)) {
                    $root->left->parent = $root;
                }
  
                switch ($root->bf) {
                    //原来左右子树等高,现在左子树增高而树增高
                    case self::EH:
                        $root->bf = self::LH;
                        //树长高
                        return TRUE;
                        break;
                    //原来左子树比右子树高,需要做左平衡处理
                    case self::LH:
                        $this->LeftBalance($root);
                        //平衡后,树并未长高
                        return FALSE;
                        break;
                    //原来右子树比左子树高,现在左右子树等高
                    case self::RH:
                        $root->bf = self::EH;
                        //树并未长高
                        return FALSE;
                        break;
                }
            } //在root的右子树中继续搜索
            else {
                //插入右子树失败
                if (!$this->insert_node($root->right, $key)) {
                    //树未长高
                    return FALSE;
                }
                //成功插入,修改平衡因子
                if (is_null($root->right->parent)) {
                    $root->right->parent = $root;
                }
                switch ($root->bf) {
                    //原来左右子树等高,现在右子树增高而树增高
                    case self::EH:
                        $root->bf = self::RH;
                        //树长高
                        return TRUE;
                        break;
                    //原来左子树比右子树高,现在左右子树等高
                    case self::LH:
                        $root->bf = self::EH;
                        return FALSE;
                        break;
                    //原来右子树比左子树高,要做右平衡处理
                    case self::RH:
                        $this->RightBalance($root);
                        //树并未长高
                        return FALSE;
                        break;
                }
            }
        }
    }
  
    /**
     * (对外)将$key插入树中
     * @param $key 待插入树的数字
     * @return null
     */
    public function Insert($key)
    {
        $this->insert_node($this->root, $key);
    }
  
    /**
     * 获取待删除的节点(删除的最终节点)
     * @param $key 待删除的数字
     * @return 最终被删除的节点
     */
    private function get_del_node($key)
    {
        $dnode = $this->search($key);
        if ($dnode == NULL) {
            throw new Exception("结点不存在!");
            return;
        }
        if ($dnode->left == NULL || $dnode->right == NULL) { #如果待删除结点无子节点或只有一个子节点,则c = dnode
            $c = $dnode;
        } else { #如果待删除结点有两个子节点,c置为dnode的直接后继,以待最后将待删除结点的值换为其后继的值
            $c = $this->successor($dnode);
        }
  
        $dnode->key = $c->key;
        return $c;
    }
  
  
    /**
     * (对内)删除指定节点,处理该结点往上结点的平衡因子
     * @param $node 最终该被删除的节点
     * @return null
     */
    private function del_node($node)
    {
        if ($node == $this->root) {
            $this->root = NULL;
            return;
        }
  
        $current = $node;
        //现在的node只有两种情况,要么只有一个子节点,要么没有子节点
        $P = $current->parent;
        //删除一个结点,第一个父节点的平衡都肯定会发生变化
        $lower = TRUE;
        while ($lower == TRUE && !is_null($P)) {
            //待删除结点是左节点
            if ($current == $P->left) {
                if($current == $node){
                    if (!is_null($current->left)) {
                        $P->left = $current->left;
                    } else {
                        $P->left = $current->left;
                    }
                }
                $P_bf = $P->bf;
                switch ($P_bf) {
                    case self::LH:
                        $P->bf = self::EH;
                        $lower = TRUE;
                        $current = $P;
                        $P = $current->parent;
                        break;
                    case self::EH:
                        $P->bf = self::RH;
                        $lower = FALSE;
                        break;
                    case self::RH:
                        $this->RightBalance($P);
                        $lower = TRUE;
                        $current = $P->parent;
                        $P = $current->parent;
                        break;
                }
            } //右结点
            else {
                if($current == $node){
                    if (!is_null($current->left)) {
                        $P->right = $current->left;
                    } else {
                        $P->right = $current->left;
                    }
                }
                $P_bf = $P->bf;
                switch ($P_bf) {
                    case self::LH:
                        $this->LeftBalance($P);
                        $lower = TRUE;
                        $current = $P->parent;
                        $P = $current->parent;
                        break;
                    case self::EH:
                        $P->bf = self::LH;
                        $lower = FALSE;
                        break;
                    case self::RH:
                        $P->bf = self::LH;
                        $lower = TRUE;
                        $current = $P;
                        $P = $current->parent;
                        break;
                }
            }
        }
    }
  
    /**
     * (对外)删除指定节点
     * @param $key 删除节点的key值
     * @return null
     */
    public function Delete($key)
    {
        $del_node = $this->get_del_node($key);
        $this->del_node($del_node);
    }
  
    /**
     * (对内)获取树的深度
     * @param $root 根节点
     * @return 树的深度
     */
    private function getdepth($root)
    {
        if ($root == NULL) {
            return 0;
        }
        $dl = $this->getdepth($root->left);
  
        $dr = $this->getdepth($root->right);
  
        return ($dl > $dr ? $dl : $dr) + 1;
    }
  
    /**
     * (对外)获取树的深度
     * @param null
     * @return null
     */
    public function Depth()
    {
        return $this->getdepth($this->root);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容