php二叉搜索,二叉搜索树-php实现 插入删除查找等操作

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

以前只是知道又这么一种树但是没怎么去了解,这次查看了算法导论上介绍的思路, 用php写了个例子。

节点类

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

BST树类

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

二叉搜索树样图

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

下面介绍下大致的操作

一  遍历

二叉搜索树可以通过简单的递归来遍历所有节点的关键词, 根据根,以及左右子树的输出顺序分为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的父节点  ,如下图

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

3) z有两个孩子

可以找到$z节点的后继或者前驱节点来替换$z, 达到删除,并且不破坏树结构的目的。 这里选后继来举例, 可以分成2种情况

假如 后继节点 为 y

a) z的右孩子就是它的后继节点

y 替换 z 节点,  y的左孩子指向 z 的 左孩子,  z的 左孩子的 父节点指向 y,  y的父节点指向  z 节点的父节点

这里由个情况要说明就是 , z 的 后继节点 的左孩子肯定为null, 假如不是null 的话那么z 的后继就是y的左孩子了, 所以 z的后继 y 肯定是没有左孩子的

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

b) z的右孩子不是它的后继节点

这情况通过转换下就可以和上面情况一致了,所以只需转换下就OK了

y的右孩子替换 y, y 的右孩子 改成 z 的右孩子,  z 的右孩子的 父节点 由 z 改为 y,  这样转换后  就和上面的情况一致了

为什么可以这样转换?

y的右孩子替换 y,   这操作 等同于 删除y 节点 操作

y改为 z 的 右孩子的 父亲,  因为 y 是z 的后继 所以  y 肯定是 z 的右边 子树 中最小的,  所以   y 可以 作为  z 的 右孩子的父亲 , 没有破坏  树的结构

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

删除代码

/**

* 移动节点

* @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);

}

}

代码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值