二叉搜索樹-php實現 插入刪除查找等操作


         二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別為二叉排序樹

   以前只是知道又這么一種樹但是沒怎么去了解,這次查看了算法導論上介紹的思路, 用php寫了個例子。

節點類

 

 

BST樹類

 

 

二叉搜索樹樣圖

 

下面介紹下大致的操作

一  遍歷

 二叉搜索樹可以通過簡單的遞歸來遍歷所有節點的關鍵詞, 根據根,以及左右子樹的輸出順序分為3種 

(左根右) 中序遍歷  [2 3 4 6 7 9 13 15 17 18 20]

(根左右) 先序遍歷  [15 6 3 2 4 7 13 9 18 17 20]

(左右根) 后序遍歷  [2 4 3 9 13 7 6 17 20 18 15]

中序遍歷 示例

    /**
     * 遍歷節點,獲取key數組
     * @param Node $node 節點
     * @param int $type 遍歷類型 0 中序 1 前序 2 后序
     * @return array
     * @author zxqc2018
     */
    public function walkTree(Node $node, int $type = 0)
    {
        $keyArr = [];
        $walkTreeFunc = function (?Node $node) use (&$keyArr, &$walkTreeFunc, $type){
            if (!is_null($node)) {
                if ($type === 1) {
                    $keyArr[] = $node->getKey();
                    $walkTreeFunc($node->getLeft());
                    $walkTreeFunc($node->getRight());
                }  else if ($type == 2) {
                    $walkTreeFunc($node->getLeft());
                    $walkTreeFunc($node->getRight());
                    $keyArr[] = $node->getKey();
                } else {
                    $walkTreeFunc($node->getLeft());
                    $keyArr[] = $node->getKey();
                    $walkTreeFunc($node->getRight());
                }
            }
        };

        $walkTreeFunc($node);

        return $keyArr;
    }

 

 二 查找節點

非遞歸查找

    /**
     * 根據key, 查找節點
     * @param int $key
     * @param Node|null $node
     * @return Node|null
     * @author zxqc2018
     */
    public function search(int $key, Node $node = null)
    {
        if (is_null($node)) {
            $node = $this->getRoot();
        }

        while (!is_null($node) && $key != $node->getKey()) {
            if ($key < $node->getKey()) {
                $node = $node->getLeft();
            } else {
                $node = $node->getRight();
            }
        }

        return $node;
    }

遞歸查找

    /**
     * 根據key, 查找節點
     * @param int $key
     * @param Node|null $node
     * @return mixed
     * @author zxqc2018
     */
    public function searchRecursion(int $key, Node $node = null)
    {
        if (is_null($node)) {
            $node = $this->getRoot();
        }

        $recursionFunc = function ($key, Node $node) use (&$recursionFunc) {
            if (is_null($node) || $node->getKey() == $key) {
                return $node;
            }

            if ($key < $node->getKey()) {
                return $recursionFunc($key, $node->getLeft());
            } else {
                return $recursionFunc($key, $node->getRight());
            }
        };
        return $recursionFunc($key, $node);
    }

 三 查找最大或小節點

最小節點

    /**
     * 查找最小節點
     * @param Node|null $node
     * @return Node|null
     * @author zxqc2018
     */
    public function findMinNode(Node $node)
    {
        if (!is_null($node)) {
            while (!is_null($node->getLeft())) {
                $node = $node->getLeft();
            }
        }
        return $node;
    }

最大節點

    /**
     * 查找最大節點
     * @param Node|null $node
     * @return Node|null
     * @author zxqc2018
     */
    public function findMaxNode(Node $node)
    {
        if (!is_null($node) && !is_null($node->getRight())) {
            $node = $this->findMaxNode($node->getRight());
        }
        return $node;
    }

四 后繼和前驅

一顆二叉搜索樹,按照中序遍歷(從小到大)后的次序,  給定某個節點, 那么 后繼 則是 此節點之后的那個節點, 前驅 則反之

查找后繼有兩種情況

1 節點的右孩子非空,   則后繼是 右節點為根的子樹種 關鍵字 最小的節點 。

2 節點的右孩子是空 並且有后繼(樹中的最大關鍵字的節點無后繼)。那么 后繼是  給點節點 最早有左孩子的底層祖先。

   拿上面樣圖中 13 這個節點的 舉例 。13的 第一個祖先 是 7 ,由於 13 是7的右孩子,所以肯定比 7 大,而 7的左孩子也肯定比 13 小 ,  以此類推, 到 6 的時候,是 祖先的 左孩子 , 說明 6 的祖先 肯定 比 13 , 也是祖先中比   13 大的 最小的節點。

后置

    /**
     * 獲取節點的后繼
     * @param Node $node
     * @return Node|null
     * @author zxqc2018
     */
    public function getSuccessor(Node $node)
    {
        //是否有右孩子
        if (!is_null($node->getRight())) {
            return $this->findMinNode($node->getRight());
        }

        $y = $node->getParent();

        //向上逐層判斷是否為祖先的右孩子
        while (!is_null($y) && $node === $y->getRight()) {
            $node = $y;
            $y = $y->getParent();
        }

        return $y;
    }

前驅

    /**
     * 獲取節點的前驅
     * @param Node $node
     * @return Node|null
     * @author zxqc2018
     */
    public function getPredecessor(Node $node)
    {
        //是否有左孩子
        if (!is_null($node->getLeft())) {
            return $this->findMaxNode($node->getLeft());
        }

        $y = $node->getParent();

        //向上逐層判斷是否為祖先的左孩子
        while (!is_null($y) && $node === $y->getLeft()) {
            $node = $y;
            $y = $y->getParent();
        }

        return $y;
    }

 

五 插入

    /**
     * 插入節點key
     * @param int $key
     * @return Node
     * @author zxqc2018
     */
    public function insert(int $key)
    {
        $x = $this->getRoot();
        $y = null;
        $z = new Node($key);

        while (!is_null($x)) {
            $y = $x;
            if ($key < $x->getKey()) {
                $x = $x->getLeft();
            } else {
                $x = $x->getRight();
            }
        }

        //設置插入節點的父節點
        $z->setParent($y);

        //假如樹還沒根節點
        if (is_null($y)) {
            $this->root = $z;
        } else if ($key < $y->getKey()) {
            $y->setLeft($z);
        } else {
            $y->setRight($z);
        }

        return $z;
    }

 

六 刪除

刪除的情況比較復雜可以分為3種

假如 刪除節點  為 z

1) z沒有孩子

    z的父節點用null 來替換 $z節點

2) z有一個孩子

  假如z有一個右孩子,  z的右孩子 替換 z, 並且 z右孩子的父節點指向 z的父節點  ,如下圖

 

3) z有兩個孩子

  可以找到$z節點的后繼或者前驅節點來替換$z, 達到刪除,並且不破壞樹結構的目的。 這里選后繼來舉例, 可以分成2種情況

  假如 后繼節點 為 y

  a) z的右孩子就是它的后繼節點

    y 替換 z 節點,  y的左孩子指向 z 的 左孩子,  z的 左孩子的 父節點指向 y,  y的父節點指向  z 節點的父節點

    這里由個情況要說明就是 , z 的 后繼節點 的左孩子肯定為null, 假如不是null 的話那么z 的后繼就是y的左孩子了, 所以 z的后繼 y 肯定是沒有左孩子的

  

 

  b) z的右孩子不是它的后繼節點

     這情況通過轉換下就可以和上面情況一致了,所以只需轉換下就OK了

     y的右孩子替換 y, y 的右孩子 改成 z 的右孩子,  z 的右孩子的 父節點 由 z 改為 y,  這樣轉換后  就和上面的情況一致了

     為什么可以這樣轉換?  

     y的右孩子替換 y,   這操作 等同於 刪除y 節點 操作

    y改為 z 的 右孩子的 父親,  因為 y 是z 的后繼 所以  y 肯定是 z 的右邊 子樹 中最小的,  所以   y 可以 作為  z 的 右孩子的父親 , 沒有破壞  樹的結構

 刪除代碼

    /**
     * 移動節點
     * @param Node $src 源節點
     * @param Node $dst 目標節點
     * @author zxqc2018
     */
    protected function transplantNode(?Node $src, Node $dst)
    {
        if (is_null($dst->getParent())) {
            $this->root = $src;
        }else if ($dst === $dst->getParent()->getLeft()) {
            $dst->getParent()->setLeft($src);
        } else {
            $dst->getParent()->setRight($src);
        }

        //源節點不空,則把源節點父節點指向目標節點的父節點
        if (!is_null($src)) {
            $src->setParent($dst->getParent());
        }
    }

    /**
     * 刪除節點
     * @param Node $node
     * @author zxqc2018
     */
    public function delete(Node $node)
    {
         if (is_null($node->getLeft())) {
            $this->transplantNode($node->getRight(), $node);
        } else if (is_null($node->getRight())) {
            $this->transplantNode($node->getLeft(), $node);
        } else {
            $successorNode = $this->getSuccessor($node);
            //刪除節點的右孩子不是后繼節點,則做相應轉換
            if ($node->getRight() !== $successorNode) {
                //后繼節點的右孩子替換后繼節點
                $this->transplantNode($successorNode->getRight(), $successorNode);
                //設置刪除節點的右孩子為后繼節點的右孩子
                $successorNode->setRight($node->getRight());
                //刪除節點的右孩子的父節點改為后繼節點
                $successorNode->getRight()->setParent($successorNode);
            }

            //后繼節點替換刪除節點
            $this->transplantNode($successorNode, $node);
            //設置刪除節點的左孩子為后繼節點的左孩子
            $successorNode->setLeft($node->getLeft());
            //刪除節點的左孩子的父節點改為后繼節點
            $successorNode->getLeft()->setParent($successorNode);
        }
    }

 

代碼地址

https://github.com/zxqc/Share


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM