二叉搜索树-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);
}
}
代码地址
二叉搜索树-php实现 插入删除查找等操作的更多相关文章
- 二叉搜索树的结构(30 分) PTA 模拟+字符串处理 二叉搜索树的节点插入和非递归遍历
二叉搜索树的结构(30 分) PTA 模拟+字符串处理 二叉搜索树的节点插入和非递归遍历 二叉搜索树的结构(30 分) 二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则 ...
- 二叉搜索树的结构(30 分) PTA 模拟+字符串处理 二叉搜索树的节点插入和非递归遍历
二叉搜索树的结构(30 分) 二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值:若它的右子树不空,则右子树上所有结点的值均大于它的根 ...
- 高度平衡的二叉搜索树(AVL树)
AVL树的基本概念 AVL树是一种高度平衡的(height balanced)二叉搜索树:对每一个结点x,x的左子树与右子树的高度差(平衡因子)至多为1. 有人也许要问:为什么要有AVL树呢?它有什么 ...
- Java二叉搜索树实现
树集合了数组(查找速度快)和链表(插入.删除速度快)的优点 二叉树是一种特殊的树,即:树中的每个节点最多只能有两个子节点 二叉搜索树是一种特殊的二叉树,即:节点的左子节点的值都小于这个节点的值,节点的 ...
- 二叉搜索树的java实现
转载请注明出处 一.概念 二叉搜索树也成二叉排序树,它有这么一个特点,某个节点,若其有两个子节点,则一定满足,左子节点值一定小于该节点值,右子节点值一定大于该节点值,对于非基本类型的比较,可以实现Co ...
- 自己动手实现java数据结构(六)二叉搜索树
1.二叉搜索树介绍 前面我们已经介绍过了向量和链表.有序向量可以以二分查找的方式高效的查找特定元素,而缺点是插入删除的效率较低(需要整体移动内部元素):链表的优点在于插入,删除元素时效率较高,但由于不 ...
- 二叉搜索树 C语言实现
1.二叉搜索树基本概念 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是一棵具有如下特性的非空二叉树: (1)若它的左子树非空,则左子树上所有结点的关键字均小于根结点的关键字: (2)若它的右子树非 ...
- java二叉搜索树原理与实现
计算机里面的数据结构 树 在计算机存储领域应用作用非常大,我之前也多次强调多磁盘的存取速度是目前计算机飞速发展的一大障碍,计算机革命性的的下一次飞跃就是看硬盘有没有质的飞跃,为什么这么说?因为磁盘是永 ...
- 算法导论 第十二章 二叉搜索树(python)
上图: 这是二叉搜索树(也有说是查找树的)基本结构:如果y是x的左子树中的一个结点,那么y.key <= x.key(如a图中的6根结点大于它左子树的每一个结点 6 >= {2,5,5}) ...
随机推荐
- Web性能权威指南 PDF扫描版
Web性能权威指南是谷歌公司高性能团队核心成员的权威之作,堪称实战经验与规范解读完美结合的产物.<Web性能权威指南>目标是涵盖Web开发者技术体系中应该掌握的所有网络及性能优化知识.全书 ...
- jQuery的选择器+实例
返回目录 jQuery的冒号选择器 表单 :input :text :password :radio :checkbox :submit :image :reset :button :file :h ...
- EIP权限工作流升级说明-2019/3/12
流程配置界面,允许流程处理按钮名称自定义:适用于流程过程中显示不同字面意思.流程节点按钮配置界面 流程处理过程中
- Binder学习笔记(二)——defaultServiceManager()返回了什么?
不管是客户端还是服务端,头部都要先调用 sp < IServiceManager > sm = defaultServiceManager(); defaultServiceManager ...
- NSString 字符串
0.字符串常用操作 自动补充方法:当字符串长度不够需要自动补充到一定的位数 OC字符串与C语言字符串之间的相互转换 1.不可变字符串的创建 // 直接创建不可变字符串 /* 在 OC 中,使用 @&q ...
- [Oracle入门级]知识概况
oracle各个版本间的主要技术更新 oracle 增加数据库创建和存储对象 oracle 8i 整体性能提升 oracle9i 实施应用集群 oracle 10g 支持网格计算 oracle 11g ...
- DockerFile一键搭建环境(一)
点击查看文件详情 FROM centos:7 COPY --chown=root:root nginx /etc/init.d/ Run set -ex \ && yum insta ...
- SIGHUP信号
SIGHUP会在以下3种情况下被发送给相应的进程:1.终端关闭时,该信号被发送到session首进程以及作为job提交的进程(即用 & 符号提交的进程)2.session首进程退出时,该信号被 ...
- arcgis图片文件
- spring boot +Thymeleaf+mybatis 集成通用PageHelper,做分页
controller: /** * 分页查询用户 * @param request * @param response * @return * @throws Exception */ @ ...