二叉搜索樹-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 */ publicfunction 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實現 插入刪除查找等操作