資料結構之PHP二分搜尋樹

thinkabel發表於2021-01-17

圖片

前面我們學習了陣列,棧,連結串列,佇列。他們都是屬於線性結構, 區別在於是順序儲存還是無序儲存。在學習連結串列的時候,我們知道連結串列裡儲存的是兩個元素,一個是當前元素值,一個是寫一個節點的元素資訊。今天我們學習的二分搜尋樹也是這種型別,不過是三個元素。

學習二分搜尋樹,首先我們來看下二分樹。既然稱為樹,顧名思義,就是來源於我們生活中的樹,一支分多支,無限延展下去的結構。在資料結構中,樹結構和現實生活中的樹是反著的。把樹根放在頂部,葉子在下,類似於這樣。

圖片

(樹結構示意圖)

一、二叉樹

什麼是二叉樹,上面的結構就是一個二叉樹,一個節點連著兩個節點。二叉樹具有唯一的根節點,每個節點都有兩個子節點。換句話也就是說,二叉樹每個節點最多有兩個子節點,二叉樹每個節點最多有一個父節點。

在樹結構中,我們稱頂級節點為根節點,下面的節點都為子節點,在子節點 的 左子樹 和 右子樹 都為空的時候,稱之為葉子節點。

圖片

(二叉樹 示意圖)

我們看到, 二叉樹不一定是全”滿”的, 就算一個節點也是二叉樹, 比如說現在只有根節點, 這個時候也是一個二叉樹, 空樹也是一個二叉樹。二叉樹具有天然的遞迴性, 為什麼這樣說呢, 我們看, 孩子又是一個新的二叉樹, 這就體現了遞迴。

二、二分搜尋樹 (Binary Search Tree)

二分搜尋樹也是一種二叉樹, 只不過比二叉樹要多一些規則:

  • 二分搜尋樹每個節點的值:

  • 大於當前節點左子樹所有節點的值

  • 小於當前節點右子樹所有節點的值

  • 每一個子樹也是一個二分搜尋樹

圖片

(二分搜尋樹 示意圖)

1. 新增元素

我們根據這個基本規則進行新增元素。從根節點出發,如果新增的元素大於根節點元素值,就需要將此元素放在根節點的右子樹上,直到待插入節點為null,就把這個元素插入該位置。看下流程圖

圖片

(二分搜尋樹新增元素 示意圖)

我們認真思考一下,如果我們想插入兩個66 或者 8 怎麼辦,這個時候, 元素應該是放在左子樹 還是 右子樹 呢?其實插入左子樹 還是 右子樹, 這個要根據實際需求來決定放在哪裡。如果我們想包含重複元素的話,我們就改下定義:**左子樹小於等於節點 或者 右子樹 大於等於 節點。**

2. 查詢和更改元素

我們之前在學習陣列, 連結串列時都有查詢指定位置的情況。但是在樹結構中,沒有位置可言,我們可以查詢樹結構中是否包含待查元素。同新增元素一樣,我們可以通過遞迴的方式,進行查詢和更改。

3. 遍歷元素

樹的遍歷元素和線性結構遍歷是不同的,線性結構是從頭到尾,對於樹來說遍歷分為以下幾種情況:

  1. 前序遍歷

  2. 中序遍歷

  3. 後序遍歷

  4. 層序遍歷

在上面的遍歷中,前序, 中序,後序 都是為深度演算法,而層序遍歷則是廣度優先的演算法。

3.1 前序遍歷

前序遍歷主要思想就是,先訪問節點元素,然後在對左右子樹進行前序遍歷操作。我們看下圖:

圖片

(前序遍歷 示意圖)

流程順序:

  • 先訪問節點元素 28, 然後 在對 28 的 左子樹進行遍歷

  • 然後訪問節點元素 20, 然後對 20 的 左子樹 進行遍歷

  • 訪問節點元素 13, 然後對 13 的左子樹進行遍歷, 發現 13 左右子樹都為空。這時訪問節點右子樹 22, 發現 22 左右字數都為空。所以遍歷遍歷結果為 20-13-22

  • 此時返回節點元素 28, 對 28的 右子樹 進行遍歷, 訪問節點元素 36,然後對 36 的 左子樹進行遍歷

  • 訪問 節點元素 30, 然後對 30 的左子樹進行遍歷, 發現 30 左右子樹都為空。這時訪問節點右子樹 42, 發現 42 左右字數都為空。所以遍歷遍歷結果為 36-30-42

*程式的結果為: 28-20-13-22-36-30-42
*

3.2 中序遍歷

中序遍歷主要思想就是,把訪問根節點元素的操作放在中間,先對左子樹進行遍歷, 然後訪問其節點元素, 最後遍歷右子樹。我們看下圖:

圖片

(中序遍歷 示意圖)

流程順序:

  • 先訪問左子樹

  • 左子樹的根節點為 20, 這時 在 對 20 的左子樹進行遍歷, 訪問 13 , 發現13為葉子節點, 在返回根節點 20 , 最後訪問 20 的右子樹 22. 結果為 13-20-22

  • 在訪問根節點元素 , 結果為28

  • 在訪問右子樹

  • 右子樹的根節點 為 36, 在對 36 的左子樹進行遍歷, 訪問 30, 發現30為葉子節點, 在返回根節點 36 , 最後訪問 36 的右子樹 42. 結果為 30-36-42

程式的結果為: 13-20-22-28-30-36-42

3.3 後序遍歷

有了前面兩個遍歷的基礎, 後序遍歷會比較簡單了. 主要思想就是,把訪問根節點元素的操作放在最後,先對左子樹進行遍歷, 然後遍歷右子樹。 最後其節點元素

程式的結果為: 13-22-20-30-42-36-28

3.4 層序遍歷

層序遍歷和前面的不同, 層序遍歷是廣度優先的演算法. 也就是一層層的進行訪問. 某一層深度訪問完 後 繼續往下訪問.

這裡我們需要藉助佇列進行實現, 主要的思想就是, 讓每一層級的節點入隊, 這樣出隊的元素 就是 左右節點的元素.

圖片

(層序遍歷 示意圖)

我們要先出根節點, 先將根節點入隊. 然後需要遍歷第二層, 先將左子樹入隊, 在入隊右子樹. 最後就是下圖所示:

圖片

(層序遍歷 示意圖)

4. 刪除元素

刪除元素時最難的一個操作, 我們先從簡單的入手, 刪除最大值和最小值。最大值我們知道,在二分搜尋樹的右子節點, 就是最大值;最小值就是左子樹的葉子節點。

圖片

(最大 最小 示意圖)

4.1 刪除最小節點

     刪除就是刪除左子樹的最小點, 左子樹分為兩種情況:
  • 若是葉子節點,直接刪除該元素即可;

  • 若無左子樹節點,右子樹節點頂替待刪除節點。

圖片

(刪除葉子節點 13 示意圖)

圖片

(刪除葉子節點20, 無左子樹 示意圖)

4.2 刪除最大節點

     刪除就是刪除右子樹的最大點, 右子樹分為兩種情況:
  • 若是葉子節點,直接刪除該元素即可;

  • 若無右子樹節點,左子樹節點頂替待刪除節點。

    這裡就不畫圖了

4.3 刪除任意節點

 刪除任意節點, 相對來講會麻煩一點,  這裡需要一個方法

One efficient way to do this, as discovered by Thomas Hibbard in 1962, is to swap the node to be deleted with its successor. The successor to a key is the next largest key in the tree and is conveniently found as the minimum key in the right subtree of the node to be deleted.

math.oxford.emory.edu/site/cs171/hi...

意思就是說, 將要刪除的節點與其後繼節點交換. 這裡 後繼 指的是 當前節點 右子樹中最小的節點進行替換當前刪除節點. 這種情況來處理左右子樹都有資料的情況. 相反我們也可以 使用 前序, 前序 指的 是當前節點 左子樹 中 最大的節點進行替換。這兩種都可以滿足。

此時, 我們需要 刪除 36 這個節點,我們使用後繼的方式看下。

圖片

(後繼示意圖)

根據定義,36的右子樹中最小的節點進行替換,右子樹只有一個節點, 那也就是 42, 所以使用 42 替換 36。

圖片

(後繼示意圖)

前序根據定一樣是一樣的。就不畫圖了。大家可以動手理解下。
三、程式實現

BinarySearchTree.php

<?php
/**
 * Created by : PhpStorm
 * User: think abel
 * Date: 2021/1/13 0013
 * Time: 22:22
 */
include 'ArrayQueue/LinkedQueue.php';

class BinarySearchTree
{
    private $root;

    private $size;

    public function __construct()
    {
        $this->root = null;
        $this->size = 0;
    }

    /**
     * Notes: 獲取 二分搜尋樹 長度
     * User: think abel
     * Date: 2021/1/13 0013
     * Time: 22:29
     *
     * @return int
     */
    public function getSize()
    {
        return $this->size;

    }

    /**
     * Notes: 二分搜尋樹是否為空
     * User: think abel
     * Date: 2021/1/13 0013
     * Time: 22:29
     *
     * @return bool
     */
    public function isEmpty()
    {
        return $this->size == 0;

    }

    /**
     * Notes: 新增元素
     * User: think abel
     * Date: 2021/1/13 0013
     * Time: 23:22
     *
     * @param $val
     */
    public function add($val)
    {
        $this->root = $this->recursion($this->root, $val);

    }

    /**
     * Notes: 遞迴插入
     * User: think abel
     * Date: 2021/1/13 0013
     * Time: 22:45
     *
     * @param BinarySearchTreeNode $node
     * @param                      $val
     *
     * @return BinarySearchTreeNode
     */
    private function recursion($node, $val)
    {
        // 暫時不包含重複值
        if ($node == null) {
            $this->size ++;
            return new BinarySearchTreeNode($val);
        }

        if ($val < $node->val) {
            $node->left = $this->recursion($node->left, $val);
        }
        elseif ($val > $node->val) {
            $node->right = $this->recursion($node->right, $val);
        }

        return $node;

    }

    /**
     * Notes: 查詢元素是否存在
     * User: think abel
     * Date: 2021/1/13 0013
     * Time: 23:24
     *
     * @param $val
     *
     * @return bool
     */
    public function contains($val)
    {
        return $this->containsRecursion($this->root, $val);
    }

    /**
     * Notes: 遞迴查詢元素是否存在
     * User: think abel
     * Date: 2021/1/13 0013
     * Time: 23:27
     *
     * @param BinarySearchTreeNode $node
     * @param                      $val
     *
     * @return bool
     */
    private function containsRecursion($node, $val)
    {
        if ($node == null) {
            return false;
        }

        if ($node->val == $val) {
            return true;
        }
        elseif ($val < $node->val) {
            return $this->containsRecursion($node->left, $val);
        }
        elseif ($val > $node->val) {
            return $this->containsRecursion($node->right, $val);
        }

    }

    /**
     * Notes: 前序遍歷
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 10:08
     *
     */
    public function preOrder()
    {
        $this->preOrderRecursion($this->root);
    }

    /**
     * Notes: 前序遍歷
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 10:08
     *
     * @param $node
     */
    private function preOrderRecursion($node)
    {
        if ($node != null) {
            echo $node->val . " ";
            $this->preOrderRecursion($node->left);
            $this->preOrderRecursion($node->right);
        }

    }

    /**
     * Notes: 非遞迴實現 前序遍歷
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 11:46
     */
    public function preOrderNonRecursion()
    {
        $stack = array();

        array_push($stack, $this->root);

        while (count($stack)) {
            $cur = array_pop($stack);
            echo $cur->val;

            if ($cur->right) {
                array_push($stack, $cur->right);
            }
            if ($cur->left) {
                array_push($stack, $cur->left);
            }
        }
    }

    /**
     * Notes: 中序遍歷
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 11:13
     */
    public function inOrder()
    {
        $this->inOrderRecursion($this->root);
    }

    /**
     * Notes: 中序遍歷
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 10:08
     *
     * @param $node
     */
    private function inOrderRecursion($node)
    {
        if ($node != null) {
            $this->inOrderRecursion($node->left);
            echo $node->val . " ";
            $this->inOrderRecursion($node->right);
        }

    }

    /**
     * Notes: 後序遍歷
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 11:13
     */
    public function postOrder()
    {
        $this->postOrderRecursion($this->root);
    }

    /**
     * Notes: 後序遍歷
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 10:08
     *
     * @param $node
     */
    private function postOrderRecursion($node)
    {
        if ($node != null) {
            $this->inOrderRecursion($node->left);
            $this->inOrderRecursion($node->right);
            echo $node->val . " ";
        }

    }

    /**
     * Notes:
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 18:08
     */
    public function levelOrder()
    {
        $queue = new LinkedQueue();
        $queue->enqueue($this->root);

        while ($queue->getSize()) {
            $removeNode = $queue->dequeue();
            echo $removeNode->val;

            if ($removeNode->left) {
                $queue->enqueue($removeNode->left);
            }

            if ($removeNode->right) {
                $queue->enqueue($removeNode->right);
            }

        }


    }

    /**
     * Notes: 輸出字元
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 11:14
     *
     * @return string
     */
    public function toString()
    {
        $str = '';
        return $str = $this->generateTreeString($this->root, 0, $str);
    }

    /**
     * Notes: 生成以node為根節點,深度為depth描述二叉樹的字串
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 10:17
     *
     * @param $node
     * @param $depth
     * @param $str
     *
     * @return string
     */
    private function generateTreeString($node, $depth, $str)
    {
        if ($node == null) {
            $str .= $this->generateDepthString($depth) . "null" . PHP_EOL;
            return $str;
        }

        $str .= $this->generateDepthString($depth) . $node->val . PHP_EOL;

        $str = $this->generateTreeString($node->left, $depth + 1, $str);
        $str = $this->generateTreeString($node->right, $depth + 1, $str);
        return $str;
    }

    private function generateDepthString($depth)
    {
        $str = '';
        for ($i = 0; $i < $depth; $i ++) {
            $str .= '--';
        }

        return $str;

    }

    /**
     * Notes: 獲取最小值
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 19:18
     *
     * @return mixed|string
     */
    public function getMinimum()
    {
        if (!$this->getSize()) {
            return "The BinarySearchTree Is Empty";
        }

        return $this->getMinimumRecursion($this->root)->val;

    }

    /**
     * Notes: 遞迴處理查詢
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 19:17
     *
     * @param BinarySearchTreeNode $node
     *
     * @return mixed
     */
    private function getMinimumRecursion(BinarySearchTreeNode $node)
    {
        if ($node->left == null) {
            return $node;
        }

        return $this->getMinimumRecursion($node->left);

    }


    /**
     * Notes: 獲取最大值
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 19:18
     *
     * @return mixed|string
     */
    public function getMaximum()
    {
        if (!$this->getSize()) {
            return "The BinarySearchTree Is Empty";
        }

        return $this->getMaximumRecursion($this->root)->val;

    }

    /**
     * Notes: 遞迴處理查詢
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 19:17
     *
     * @param BinarySearchTreeNode $node
     *
     * @return mixed
     */
    private function getMaximumRecursion(BinarySearchTreeNode $node)
    {
        if ($node->right == null) {
            return $node;
        }

        return $this->getMaximumRecursion($node->right);

    }

    /**
     * Notes: 移除最小節點
     * User: think abel
     * Date: 2021/1/14 0014
     * Time: 21:27
     *
     * @return mixed|string
     */
    public function removeMin()
    {
        $ret        = $this->getMinimum();
        $this->root = $this->removeMinRecursion($this->root);
        return $ret;

    }

    /**
     * Notes: 移除最小節點
     * User: think abel
     * Date: 2021/1/14 0014
     * Time: 21:33
     *
     * @param BinarySearchTreeNode $node
     *
     * @return BinarySearchTreeNode|null
     */
    private function removeMinRecursion(BinarySearchTreeNode $node)
    {
        // 如果當前節點的有節點為空, 說明沒有左子樹, 將右子樹頂替給左子樹
        if ($node->left == null) {
            $rightNode   = $node->right;
            $node->right = null;
            $this->size --;
            return $rightNode;
        }

        // 繼續遞迴查詢最大值, 直到 左子樹 為空 停止
        $node->left = $this->removeMinRecursion($node->left);
        return $node;
    }

    /**
     * Notes: 遞迴移除最大節點
     * User: think abel
     * Date: 2021/1/14 0014
     * Time: 21:27
     *
     * @return mixed|string
     */
    public function removeMax()
    {
        $ret        = $this->getMaximum();
        $this->root = $this->removeMaxRecursion($this->root);
        return $ret;

    }

    /**
     * Notes: 遞迴移除最大節點
     * User: think abel
     * Date: 2021/1/14 0014
     * Time: 21:33
     *
     * @param BinarySearchTreeNode $node
     *
     * @return BinarySearchTreeNode|null
     */
    private function removeMaxRecursion(BinarySearchTreeNode $node)
    {
        // 如果當前節點的有節點為空, 說明沒有右子樹, 將左子樹頂替給右子樹
        if ($node->right == null) {
            $leftNode   = $node->left;
            $node->left = null;
            $this->size --;
            return $leftNode;
        }

        // 繼續遞迴查詢最大值, 直到 右子樹 為空 停止
        $node->right = $this->removeMaxRecursion($node->right);
        return $node;
    }


    public function removeArbitraryNode($val)
    {
        $this->removeArbitraryNodeRecursion($this->root, $val);
    }

    /**
     * Notes: 刪除以 node 為根節點的二分搜尋樹中 值為 val 的節點
     * User: think abel
     * Date: 2021/1/14 0014
     * Time: 22:04
     *
     * @param BinarySearchTreeNode $node
     * @param                      $val
     *
     * @return BinarySearchTreeNode 返回刪除節點後新的二分搜尋樹的跟
     */
    private function removeArbitraryNodeRecursion($node, $val)
    {
        if ($node == null) {
            return null;
        }

        if ($val < $node->val) {
            $node->left = $this->removeArbitraryNodeRecursion($node->left, $val);
        }
        elseif ($val > $node->val) {
            $node->right = $this->removeArbitraryNodeRecursion($node->right, $val);
        }
        elseif ($val == $node->val) {
            // 待刪除節點左子樹為空的情況
            if ($node->left == null) {
                $rightNode   = $node->right;
                $node->right = null;
                $this->size --;
                return $rightNode;
            }

            // 待刪除節點右子樹為空的情況
            if ($node->right == null) {
                $leftNode   = $node->left;
                $node->left = null;
                $this->size --;
                return $leftNode;
            }

            /**
             * 待刪除節點左右子樹都不為空的情況
             * 找到比待刪節點大的最小節點, 就是待刪除節點的右子樹上最小的節點 (後繼)
             * 用這個節點頂替待刪除的節點位置
             *
             * @var BinarySearchTreeNode $successor
             */
            $successor        = $this->getMinimumRecursion($node->right);
            $successor->right = $this->removeMinRecursion($node->right);
            $successor->left  = $node->left;
            $node->left       = $node->right = null;
            return $successor;

            /**
             * 待刪除節點左右子樹都不為空的情況
             * 找到比待刪節點小的最大節點, 就是待刪除節點的左子樹上最大的節點 (前驅)
             * 用這個節點頂替待刪除的節點位置
             *
             * @var BinarySearchTreeNode $predecessor
             */
//            $predecessor        = $this->getMaximumRecursion($node->left);
//            $predecessor->left  = $this->removeMaxRecursion($node->left);
//            $predecessor->right = $node->right;
//
//            $node->left = $node->right = null;
//            return $predecessor;
        }

        return $node;

    }
}

/**
 * node 節點
 * Class Node
 */
class BinarySearchTreeNode
{
    public $val;

    public $left;

    public $right;

    public function __construct($val)
    {
        $this->val   = $val;
        $this->left  = null;
        $this->right = null;
    }

}

LinkedQueue.php

<?php

include 'LinkedList/LinkedList.php';

class LinkedQueue
{
    // 連結串列頭部
    private $head;

    // 連結串列尾部
    private $tail;

    // 連結串列大小
    private $size;

    /**
     * 初始化
     * LinkedQueue constructor.
     */
    public function __construct()
    {
        $this->head = null;
        $this->tail = null;
        $this->size = 0;
    }

    /**
     * Notes: 獲取連結串列大小
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 18:30
     *
     * @return int
     */
    public function getSize()
    {
        return $this->size;
    }

    /**
     * Notes: 入隊
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 18:40
     *
     * @param $val
     */
    public function enqueue($val)
    {
        if ($this->head == null) {
            $this->tail = $this->head = new linkedNode($val, null);
        }
        else {
            $node             = new linkedNode($val, null);
            $this->tail->next = $node;
            $this->tail       = $node;
        }
        $this->size ++;
    }

    /**
     * Notes: 出隊
     * Author: PhpStorm
     * Date: 2021/1/14 0014
     * Time: 18:41
     *
     */
    public function dequeue()
    {
        if ($this->size == null) {
            return "The Queue Is Empty";
        }

        $node       = $this->head;
        $this->head = $node->next;
        $this->size --;

        if ($node->next == null) {
            $this->tail = null;
        }
        return $node->val;
    }

}

class linkedNode
{
    public $val;

    public $next;

    public function __construct($val, $next)
    {
        $this->val  = $val;
        $this->next = $next;
    }
}

LinkedList.php

<?php
/**
 * Created by : PhpStorm
 * User: think abel
 * Date: 2021/1/11 0011
 * Time: 22:12
 */

class LinkedList
{
    // 連結串列虛擬頭結點
    private $dummyHead;

    // 連結串列的元素數
    private $size;

    /**
     * LinkedList constructor.
     */
    public function __construct()
    {
        $this->dummyHead = new Node(null, null);
        $this->size      = 0;

    }

    /**
     * Notes: 獲取連結串列中的元素個數
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 22:28
     *
     * @return int
     */
    public function getSize(): int
    {
        return $this->size;

    }

    /**
     * Notes: 連結串列是否為空
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 22:28
     *
     * @return bool
     */
    public function isEmpty(): bool
    {
        return $this->size == 0;

    }

    /**
     * Notes: 在連結串列的第 index 處 新增新的元素 e
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 22:45
     *
     * @param $index
     * @param $e
     *
     * @return string
     */
    public function add($index, $e)
    {
        try {
            if ($index < 0 || $index > $this->size) {
                throw new Exception("新增失敗, index require >= 0 and <= " . $this->size);
            }

            /**
             * @var Node $prev 為虛擬頭結點, 所以遍歷 輸入的 index 次 就是前一個節點
             */
            $prev = $this->dummyHead;
            for ($i = 0; $i < $index; $i ++) {
                $prev = $prev->next;
            }

            /**
             * 通過 當前傳遞的 資料 和 上一個節點所指向的下一個節點資訊 來建立 當前 節點
             * 並把 上一個節點 所指向的下一個節點資訊 變為當前建立的節點
             */
            $prev->next = new Node($e, $prev->next);
            $this->size ++;
        }
        catch (Exception $e) {
            return $e->getMessage();
        }

    }

    /**
     * Notes: 在連結串列頭新增新的元素 e
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 22:34
     *
     * @param $e
     */
    public function addFirst($e)
    {
        $this->add(0, $e);

    }

    /**
     * Notes: 向連結串列末尾新增元素 e
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 23:23
     */
    public function addLast($e)
    {
        $this->add($this->size, $e);

    }

    /**
     * Notes: 獲取第 index 位置的 元素
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 23:36
     *
     * @param int $index
     *
     * @return string
     */
    public function get(int $index)
    {
        try {
            if ($index < 0 || $index > $this->size) {
                throw new Exception("新增失敗, index require >= 0 and <= " . $this->size);
            }

            /**
             * @var Node $cur 當前節點的下一個節點, 因為前面有一個虛擬節點
             */
            $cur = $this->dummyHead->next;
            for ($i = 0; $i < $index; $i ++) {
                $cur = $cur->next;
            }

            return $cur->e;
        }
        catch (Exception $e) {
            return $e->getMessage();
        }

    }

    /**
     * Notes: 獲取第一個元素
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 23:40
     *
     * @return string
     */
    public function getFirst()
    {
        return $this->get(0);

    }

    /**
     * Notes: 獲取最後一個元素
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 23:40
     *
     * @return string
     */
    public function getLast()
    {
        return $this->get($this->size - 1);

    }

    /**
     * Notes: 更新一個元素
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 23:44
     *
     * @param int $index
     * @param     $e
     *
     * @return string
     */
    public function set(int $index, $e)
    {
        try {
            if ($index < 0 || $index > $this->size) {
                throw new Exception("修改失敗, index require >= 0 and <= " . $this->size);
            }

            /**
             * @var Node $cur 當前節點的下一個節點, 因為前面有一個虛擬節點
             */
            $cur = $this->dummyHead->next;
            for ($i = 0; $i < $index; $i ++) {
                $cur = $cur->next;
            }

            $cur->e = $e;

        }
        catch (Exception $e) {
            return $e->getMessage();
        }

    }

    /**
     * Notes: 查詢元素是否存在
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 23:49
     *
     * @param $e
     *
     * @return bool
     */
    public function contains($e): bool
    {
        /**
         * @var Node $cur
         */
        $cur = $this->dummyHead->next;

        /**
         * 不知道遍歷多少次, 使用 while 迴圈
         * 如果 當前的元素 == $e 說明 存在, 返回true
         * 否則 將 當前的下一個元素 賦值給 當前元素 繼續遍歷
         * 如果 都沒有, 說明不存在, 返回 false
         */
        while ($cur != null) {
            if ($cur->e == $e) {
                return true;
            }

            $cur = $cur->next;
        }

        return false;

    }

    /**
     * Notes: 刪除 index 位置的元素
     * User: think abel
     * Date: 2021/1/12 0012
     * Time: 0:15
     *
     * @param int $index
     *
     * @return string|null
     */
    public function remove(int $index)
    {
        try {
            if ($index < 0 || $index > $this->size) {
                throw new Exception("刪除失敗, index require >= 0 and <= " . $this->size);
            }

            /**
             * @var Node $prev 當前節點的下一個節點, 因為前面有一個虛擬節點
             */
            $prev = $this->dummyHead;
            for ($i = 0; $i < $index; $i ++) {
                $prev = $prev->next;
            }

            /**
             * @var Node $removeNode
             */
            $removeNode = $prev->next;
            $prev->next = $removeNode->next;

            $removeElement = $removeNode->e;
            $removeNode    = null;
            $this->size --;

            return $removeElement . PHP_EOL;


        }
        catch (Exception $e) {
            return $e->getMessage();
        }

    }

    /**
     * Notes: 刪除第一個元素
     * User: think abel
     * Date: 2021/1/12 0012
     * Time: 0:16
     *
     * @return string|null
     */
    public function removeFirst()
    {
        return $this->remove(0);
    }

    /**
     * Notes: 刪除最後一個元素
     * User: think abel
     * Date: 2021/1/12 0012
     * Time: 0:16
     *
     * @return string|null
     */
    public function removeLast()
    {
        return $this->remove($this->size - 1);
    }

    /**
     * Notes: 列印輸出
     * User: think abel
     * Date: 2021/1/11 0011
     * Time: 23:52
     *
     * @return string
     */
    public function toString(): string
    {
        /**
         * @var Node $cur
         */
        $cur = $this->dummyHead->next;

        $str = '';
        for ($cur; $cur != null; $cur = $cur->next) {
            $str .= $cur->e . '-->';
        }

        $str .= 'NULL';
        return $str . PHP_EOL;

    }
}

/**
 * 建立節點類
 * Class Node
 */
class Node
{
    // 節點元素
    public $e;

    // 指向下一個節點資訊
    public $next;

    /**
     * Node constructor.
     *
     * @param null $e
     * @param null $next
     */
    public function __construct($e = null, $next = null)
    {
        $this->e    = $e;
        $this->next = $next;

    }

}

index.php

include 'BinarySearchTree/BinarySearchTree.php';
$binary = new BinarySearchTree();
$binary->add(28);
$binary->add(20);
$binary->add(15);
$binary->add(22);
$binary->add(36);
$binary->add(30);
$binary->add(42);

print_r($binary);
echo PHP_EOL;
var_dump($binary->contains(5));

print_r($binary->toString());

echo $binary->preOrder();
echo $binary->inOrder();
echo $binary->postOrder();
echo $binary->preOrderNonRecursion();
echo $binary->getMinimum();
echo $binary->getMaximum();
echo $binary->removeMin();
echo PHP_EOL;
echo $binary->inOrder();
echo PHP_EOL;

echo $binary->removeMax();
echo PHP_EOL;
echo $binary->inOrder();
echo PHP_EOL;

echo $binary->removeArbitraryNode(20);
echo PHP_EOL;
echo $binary->inOrder();

資料結構之PHP二分搜尋樹

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章