1. 程式人生 > >二叉搜索樹-php實現 插入刪除查找等操作

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

har 思路 ron prot del 但是 底層 所有 tro

二叉查找樹(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

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