首頁 後端開發 php教程 PHP二元樹(三):紅黑樹

PHP二元樹(三):紅黑樹

Dec 27, 2016 am 09:30 AM

關於紅黑樹的原理網上的資源就挺多的,而且情況有點小複雜,所以在這裡我就不再陳述了,直接上代碼吧:

<!--?php
/**
 * author:zhongjin
 * time:2016/10/20 11:53
 * description: 红黑树
 */
//结点
class Node
{
    public $key;
    public $parent;
    public $left;
    public $right;
    public $IsRed;  //分辨红节点或黑节点
 
    public function __construct($key, $IsRed = TRUE)
    {
        $this--->key = $key;
        $this->parent = NULL;
        $this->left = NULL;
        $this->right = NULL;
        //插入结点默认是红色
        $this->IsRed = $IsRed;
    }
}
 
//红黑树
class Rbt
{
    public $root;
 
    /**
     * 初始化树结构
     * @param $arr 初始化树结构的数组
     * @return null
     */
    public function init($arr)
    {
        //根节点必须是黑色
        $this->root = new Node($arr[0], FALSE);
        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->IsRed ? &#39;r&#39; : &#39;b&#39;) . &#39;  &#39;;
            $this->mid_order($root->right);
        }
    }
 
    /**
     * (对外)中序遍历
     * @param null
     * @return null
     */
    public function MidOrder()
    {
        $this->mid_order($this->root);
    }
 
    /**
     * 查找树中是否存在$key对应的节点
     * @param $key 待搜索数字
     * @return $key对应的节点
     */
    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;
    }
 
    /**
     * 将以$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->right){
                $P->right = $R;
            }else{
                $P->left = $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;
        }
    }
 
    /**
     * 查找树中的最小关键字
     * @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 前驱节点引用
     */
    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 后继节点引用
     */
    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;
    }
 
    /**
     * 将$key插入树中
     * @param $key 待插入树的数字
     * @return null
     */
    public function Insert($key)
    {
        if (!is_null($this->search($key))) {
            throw new Exception(&#39;结点&#39; . $key . &#39;已存在,不可插入!&#39;);
        }
        $root = $this->root;
        $inode = new Node($key);
        $current = $root;
        $prenode = NULL;
        //为$inode找到合适的插入位置
        while ($current != NULL) {
            $prenode = $current;
            if ($current->key > $inode->key) {
                $current = $current->left;
            } else {
                $current = $current->right;
            }
        }
 
        $inode->parent = $prenode;
        //如果$prenode == NULL, 则证明树是空树
        if ($prenode == NULL) {
            $this->root = $inode;
        } else {
            if ($inode->key < $prenode->key) {
                $prenode->left = $inode;
            } else {
                $prenode->right = $inode;
            }
        }
 
        //将它重新修正为一颗红黑树
        $this->InsertFixUp($inode);
    }
 
    /**
     * 对插入节点的位置及往上的位置进行颜色调整
     * @param $inode 插入的节点
     * @return null
     */
    private function InsertFixUp($inode)
    {
        //情况一:需要调整条件,父节点存在且父节点的颜色是红色
        while (($parent = $inode->parent) != NULL && $parent->IsRed == TRUE) {
            //祖父结点:
            $gparent = $parent->parent;
 
            //如果父节点是祖父结点的左子结点,下面的else与此相反
            if ($parent == $gparent->left) {
                //叔叔结点
                $uncle = $gparent->right;
 
                //case1:叔叔结点也是红色
                if ($uncle != NULL && $uncle->IsRed == TRUE) {
                    //将父节点和叔叔结点都涂黑,将祖父结点涂红
                    $parent->IsRed = FALSE;
                    $uncle->IsRed = FALSE;
                    $gparent->IsRed = TRUE;
                    //将新节点指向祖父节点(现在祖父结点变红,可以看作新节点存在)
                    $inode = $gparent;
                    //继续while循环,重新判断
                    continue;   //经过这一步之后,组父节点作为新节点存在(跳到case2)
                }
 
                //case2:叔叔结点是黑色,且当前结点是右子节点
                if ($inode == $parent->right) {
                    //以父节点作为旋转结点做左旋转处理
                    $this->L_Rotate($parent);
                    //在树中实际上已经转换,但是这里的变量的指向还没交换,
                    //将父节点和字节调换一下,为下面右旋做准备
                    $temp = $parent;
                    $parent = $inode;
                    $inode = $temp;
                }
 
                //case3:叔叔结点是黑色,而且当前结点是父节点的左子节点
                $parent->IsRed = FALSE;
                $gparent->IsRed = TRUE;
                $this->R_Rotate($gparent);
            } //如果父节点是祖父结点的右子结点,与上面完全相反
            else {
                //叔叔结点
                $uncle = $gparent->left;
 
                //case1:叔叔结点也是红色
                if ($uncle != NULL && $uncle->IsRed == TRUE) {
                    //将父节点和叔叔结点都涂黑,将祖父结点涂红
                    $parent->IsRed = FALSE;
                    $uncle->IsRed = FALSE;
                    $gparent->IsRed = TRUE;
                    //将新节点指向祖父节点(现在祖父结点变红,可以看作新节点存在)
                    $inode = $gparent;
                    //继续while循环,重新判断
                    continue;   //经过这一步之后,组父节点作为新节点存在(跳到case2)
                }
 
                //case2:叔叔结点是黑色,且当前结点是左子节点
                if ($inode == $parent->left) {
                    //以父节点作为旋转结点做右旋转处理
                    $this->R_Rotate($parent);
                    //在树中实际上已经转换,但是这里的变量的指向还没交换,
                    //将父节点和字节调换一下,为下面右旋做准备
                    $temp = $parent;
                    $parent = $inode;
                    $inode = $temp;
                }
 
                //case3:叔叔结点是黑色,而且当前结点是父节点的右子节点
                $parent->IsRed = FALSE;
                $gparent->IsRed = TRUE;
                $this->L_Rotate($gparent);
            }
        }
        //情况二:原树是根节点(父节点为空),则只需将根节点涂黑
        if ($inode == $this->root) {
            $this->root->IsRed = FALSE;
            return;
        }
 
        //情况三:插入节点的父节点是黑色,则什么也不用做
        if ($inode->parent != NULL && $inode->parent->IsRed == FALSE) {
            return;
        }
    }
 
    /**
     * (对外)删除指定节点
     * @param $key 删除节点的key值
     * @return null
     */
    function Delete($key)
    {
        if (is_null($this->search($key))) {
            throw new Exception(&#39;结点&#39; . $key . "不存在,删除失败!");
        }
        $dnode = $this->search($key);
        if ($dnode->left == NULL || $dnode->right == NULL) { #如果待删除结点无子节点或只有一个子节点,则c = dnode
            $c = $dnode;
        } else { #如果待删除结点有两个子节点,c置为dnode的直接后继,以待最后将待删除结点的值换为其后继的值
            $c = $this->successor($dnode);
        }
 
        //为了后面颜色处理做准备
        $parent = $c->parent;
 
        //无论前面情况如何,到最后c只剩下一边子结点
        if ($c->left != NULL) {    //这里不会出现,除非选择的是删除结点的前驱
            $s = $c->left;
        } else {
            $s = $c->right;
        }
 
        if ($s != NULL) { #将c的子节点的父母结点置为c的父母结点,此处c只可能有1个子节点,因为如果c有两个子节点,则c不可能是dnode的直接后继
            $s->parent = $c->parent;
        }
 
        if ($c->parent == NULL) { #如果c的父母为空,说明c=dnode是根节点,删除根节点后直接将根节点置为根节点的子节点,此处dnode是根节点,且拥有两个子节点,则c是dnode的后继结点,c的父母就不会为空,就不会进入这个if
            $this->root = $s;
        } else if ($c == $c->parent->left) { #如果c是其父节点的左右子节点,则将c父母的左右子节点置为c的左右子节点
            $c->parent->left = $s;
        } else {
            $c->parent->right = $s;
        }
 
        $dnode->key = $c->key;
 
        $node = $s;
 
        //c的结点颜色是黑色,那么会影响路径上的黑色结点的数量,必须进行调整
        if ($c->IsRed == FALSE) {
            $this->DeleteFixUp($node,$parent);
        }
    }
 
    /**
     * 删除节点后对接点周围的其他节点进行调整
     * @param $key 删除节点的子节点和父节点
     * @return null
     */
    private function DeleteFixUp($node,$parent)
    {
        //如果待删结点的子节点为红色,直接将子节点涂黑
        if ($node != NULL && $node->IsRed == TRUE) {
            $node->IsRed = FALSE;
            return;
        }
 
 
        //如果是根节点,那就直接将根节点置为黑色即可
        while (($node == NULL || $node->IsRed == FALSE) && ($node != $this->root)) {
            //node是父节点的左子节点,下面else与这里相反
            if ($node == $parent->left) {
                $brother = $parent->right;
 
                //case1:兄弟结点颜色是红色(父节点和兄弟孩子结点都是黑色)
                //将父节点涂红,将兄弟结点涂黑,然后对父节点进行左旋处理(经过这一步,情况转换为兄弟结点颜色为黑色的情况)
                if ($brother->IsRed == TRUE) {
                    $brother->IsRed = FALSE;
                    $parent->IsRed = TRUE;
                    $this->L_Rotate($parent);
                    //将情况转化为其他的情况
                    $brother = $parent->right;  //在左旋处理后,$parent->right指向的是原来兄弟结点的左子节点
                }
 
                //以下是兄弟结点为黑色的情况
 
                //case2:兄弟结点是黑色,且兄弟结点的两个子节点都是黑色
                //将兄弟结点涂红,将当前结点指向其父节点,将其父节点指向当前结点的祖父结点。
                if (($brother->left == NULL || $brother->left->IsRed == FALSE) && ($brother->right == NULL || $brother->right->IsRed == FALSE)) {
                    $brother->IsRed = TRUE;
                    $node = $parent;
                    $parent = $node->parent;
                } else {
                    //case3:兄弟结点是黑色,兄弟结点的左子节点是红色,右子节点为黑色
                    //将兄弟结点涂红,将兄弟节点的左子节点涂黑,然后对兄弟结点做右旋处理(经过这一步,情况转换为兄弟结点颜色为黑色,右子节点为红色的情况)
                    if ($brother->right == NULL || $brother->right->IsRed == FALSE) {
                        $brother->IsRed = TRUE;
                        $brother->left->IsRed = FALSE;
 
                        $this->R_Rotate($brother);
                        //将情况转换为其他情况
                        $brother = $parent->right;
                    }
 
                    //case4:兄弟结点是黑色,且兄弟结点的右子节点为红色,左子节点为任意颜色
                    //将兄弟节点涂成父节点的颜色,再把父节点涂黑,将兄弟结点的右子节点涂黑,然后对父节点做左旋处理
                    $brother->IsRed = $parent->IsRed;
                    $parent->IsRed = FALSE;
 
                    $brother->right->IsRed = FALSE;
                    $this->L_Rotate($parent);
                    //到了第四种情况,已经是最基本的情况了,可以直接退出了
                    $node = $this->root;
                    break;
                }
            } //node是父节点的右子节点
            else {
                $brother = $parent->left;
 
                //case1:兄弟结点颜色是红色(父节点和兄弟孩子结点都是黑色)
                //将父节点涂红,将兄弟结点涂黑,然后对父节点进行右旋处理(经过这一步,情况转换为兄弟结点颜色为黑色的情况)
                if ($brother->IsRed == TRUE) {
                    $brother->IsRed = FALSE;
                    $parent->IsRed = TRUE;
                    $this->R_Rotate($parent);
                    //将情况转化为其他的情况
                    $brother = $parent->left;  //在右旋处理后,$parent->left指向的是原来兄弟结点的右子节点
                }
 
                //以下是兄弟结点为黑色的情况
 
                //case2:兄弟结点是黑色,且兄弟结点的两个子节点都是黑色
                //将兄弟结点涂红,将当前结点指向其父节点,将其父节点指向当前结点的祖父结点。
                if (($brother->left == NULL || $brother->left->IsRed == FALSE) && ($brother->right == NULL || $brother->right->IsRed == FALSE)) {
                    $brother->IsRed = TRUE;
                    $node = $parent;
                    $parent = $node->parent;
                } else {
                    //case3:兄弟结点是黑色,兄弟结点的右子节点是红色,左子节点为黑色
                    //将兄弟结点涂红,将兄弟节点的左子节点涂黑,然后对兄弟结点做左旋处理(经过这一步,情况转换为兄弟结点颜色为黑色,右子节点为红色的情况)
                    if ($brother->left == NULL || $brother->left->IsRed == FALSE) {
                        $brother->IsRed = TRUE;
                        $brother->right = FALSE;
                        $this->L_Rotate($brother);
                        //将情况转换为其他情况
                        $brother = $parent->left;
                    }
 
                    //case4:兄弟结点是黑色,且兄弟结点的左子节点为红色,右子节点为任意颜色
                    //将兄弟节点涂成父节点的颜色,再把父节点涂黑,将兄弟结点的右子节点涂黑,然后对父节点左左旋处理
                    $brother->IsRed = $parent->IsRed;
                    $parent->IsRed = FALSE;
                    $brother->left->IsRed = FALSE;
                    $this->R_Rotate($parent);
                    $node = $this->root;
                    break;
                }
            }
        }
        if ($node != NULL) {
            $this->root->IsRed = FALSE;
        }
    }
 
    /**
     * (对内)获取树的深度
     * @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);
    }
}
登入後複製

調試的時候你們可以調用中序遍歷來做,我在上一篇部落格中提供了PHP實現的二元樹圖形化,有了視覺上的幫助就能更好的幫助我們進行調試,詳細大家可以訪問我的上一篇博客:《利用PHP實現二叉樹的圖形顯示》

 以上是PHP二元樹(三):紅黑樹的內容,更多相關內容請關注PHP中文網(www.php.cn)!


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

解釋PHP中晚期靜態結合的概念。 解釋PHP中晚期靜態結合的概念。 Mar 21, 2025 pm 01:33 PM

文章討論了PHP 5.3中介紹的PHP中的晚期靜態結合(LSB),允許靜態方法的運行時間分辨率調用以更靈活的繼承。 LSB的實用應用和潛在的觸摸

框架安全功能:防止漏洞。 框架安全功能:防止漏洞。 Mar 28, 2025 pm 05:11 PM

文章討論了框架中的基本安全功能,以防止漏洞,包括輸入驗證,身份驗證和常規更新。

自定義/擴展框架:如何添加自定義功能。 自定義/擴展框架:如何添加自定義功能。 Mar 28, 2025 pm 05:12 PM

本文討論了將自定義功能添加到框架上,專注於理解體系結構,識別擴展點以及集成和調試的最佳實踐。

如何用PHP的cURL庫發送包含JSON數據的POST請求? 如何用PHP的cURL庫發送包含JSON數據的POST請求? Apr 01, 2025 pm 03:12 PM

使用PHP的cURL庫發送JSON數據在PHP開發中,經常需要與外部API進行交互,其中一種常見的方式是使用cURL庫發送POST�...

描述紮實的原則及其如何應用於PHP的開發。 描述紮實的原則及其如何應用於PHP的開發。 Apr 03, 2025 am 12:04 AM

SOLID原則在PHP開發中的應用包括:1.單一職責原則(SRP):每個類只負責一個功能。 2.開閉原則(OCP):通過擴展而非修改實現變化。 3.里氏替換原則(LSP):子類可替換基類而不影響程序正確性。 4.接口隔離原則(ISP):使用細粒度接口避免依賴不使用的方法。 5.依賴倒置原則(DIP):高低層次模塊都依賴於抽象,通過依賴注入實現。

ReactPHP的非阻塞特性究竟是什麼?如何處理其阻塞I/O操作? ReactPHP的非阻塞特性究竟是什麼?如何處理其阻塞I/O操作? Apr 01, 2025 pm 03:09 PM

深入解讀ReactPHP的非阻塞特性ReactPHP的一段官方介紹引起了不少開發者的疑問:“ReactPHPisnon-blockingbydefault....

See all articles