二叉排序 / 搜索树
基本介绍
1.二叉排序/搜索树:BST(Binary Sort/Search Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大
2.如果有相同的值,可以将该节点放在左子节点或右子节点,或不允许键值相等的节点
3.可高效的完成对数据的查询、添加、删除
4.缺陷:所有数据都在一侧子树,类似单链表,因为要比较另一侧子树,查询速度比单链表更慢
5.前驱节点:值小于当前节点的最大值节点
6.后继节点:值大于当前节点的最小值节点
7.在一棵二叉查找树中,所有操作在最坏情况下所需的时间都和树的高度成正比,树的所有操作都沿着树的一条或两条路径行进。根据定义,路径的长度不可能大于树的高度
二叉排序树删除节点思路分析
删除根节点
1.若找到要删除的结点target为根节点
2.判断根节点左右子节点是否为空
3.左右子节点不为空:找root右子树最小值的结点/左子树最大值的节点,用一个临时变量temp,将最小结点的值/最大节点的值保存,删除该最小结点/最大节点,用temp覆盖root的值
4.只有左节点:root = root.left
5.只有右节点:root = root.right
删除叶子节点
1.找到要删除的结点target
2.找到target的父结点parent
3.确定target是parent的左子结点还是右子结点
4.根据前面的情况来对应删除:左子结点parent.left = null;右子结点parent.right = null
删除非根、非叶子节点
有两颗子树:
1.找到要删除的结点target
2.找到target的父结点parent
3.找target右子树最小值的结点/左子树最大值的节点
4.用一个临时变量temp,将最小结点的值/最大节点的值保存
5.删除该最小结点/最大节点,用temp覆盖target的值
6.target.value = temp
只有一颗子树:
1.找到要删除的结点target
2.找到target的父结点parent
3.确定target的子结点是左子结点还是右子结点
4.target是parent的左子结点还是右子结点
(1)如果target有左子结点;如果target是parent的左子结点:parent.left = target.left;如果target是parent的右子结点:parent.right = target.left
(2)如果target有右子结点;如果target是parent的左子结点:parent.left = target.right;如果target是parent 的右子结点:parent.right = target.right
二叉排序树的添加规则影响删除节点
1.若添加相同值的节点,放入相同值节点的右子树
(1)删除根节点:左右子节点不为空,找root右子树最小值的节点,用一个临时变量temp,将最小节点的值保存,删除该最小节点,用temp覆盖root的值
(2)删除非根、非叶子节点:有两颗子树,找target右子树最小值的节点,用一个临时变量temp,将最小节点的值保存,删除该最小节点,用temp覆盖target的值
2.若添加相同值的节点,放入相同值节点的左子树
(1)删除根节点:左右子节点不为空,找root左子树最大值的节点,用一个临时变量temp,将最大节点的值保存,删除该最大节点,用temp覆盖root的值
(2)删除非根、非叶子节点:有两颗子树,找target左子树最大值的节点,用一个临时变量temp,将最大节点的值保存,删除该最大节点,用temp覆盖target的值
3.添加有相同值的节点,添加的节点放在相同值节点的尾部;删除有相同值的节点,删除找到的第一个节点
代码实现
public class BinarySortTree {
public Node root;
//添加节点,若有相同值的节点,则插入相同值节点的尾部
public void add(Node node) {
if (root == null) {
root = node;
} else {
root.add(node);
}
}
//查找节点
public Node search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
//查找节点的父节点
public Node searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
//删除节点,若有相同值的节点,删除找到的第一个节点
public void delete(int value) {
if (root == null) {
return;
}
Node target = search(value);//找待删除的结点target
if (target == null) {//没有找到
return;
}
Node parent = searchParent(value);//找到target的父结点parent
if (parent == null) {//说明待删除节点是root
deleteRoot();
} else if (target.left == null && target.right == null) {//说明待删除节点是叶子节点
deleteLeaf(parent, value);
} else {//说明删除非根、非叶子节点
deleteNonLeaf(target, parent, value);
}
}
//删除根节点
public void deleteRoot() {
if (root.left != null && root.right != null) {
//删除root的后继节点,并用其值覆盖root的值
root.value = deleteRightTreeMin(root);
//删除root的前驱节点,并用其值覆盖root的值
//root.value = deleteLeftTreeMax(root);
} else if (root.left != null) {
root = root.left;
} else if (root.right != null) {//
root = root.right;
} else {//root.left == null && root.right == null
root = null;
}
}
//删除叶子节点
public void deleteLeaf(Node parent, int value) {
//确定target是parent的左子节点还是右子节点
if (parent.left != null && parent.left.value == value) {
parent.left = null;
} else if (parent.right != null && parent.right.value == value) {
parent.right = null;
}
}
//删除非根、非叶子节点
public void deleteNonLeaf(Node target, Node parent, int value) {
//要删除的节点有两个子节点
if (target.left != null && target.right != null) {
//删除target的后继节点,并用其值覆盖target的值
target.value = deleteRightTreeMin(target);
//或删除target的前驱节点,并用其值覆盖target的值
//target.value = deleteLeftTreeMax(target);
} else if (target.left != null) {//要删除的节点只有一个左子节点
if (parent.left.value == value) {//要删除的节点是父节点的左子节点
parent.left = target.left;
} else {//parent.right.value == value,要删除的节点是父节点的右子节点
parent.right = target.left;
}
} else {//target.right != null,要删除的节点只有一个右子节点
if (parent.left.value == value) {//要删除的节点是父节点的左子节点
parent.left = target.right;
} else {//parent.right.value == value,要删除的节点是父节点的右子节点
parent.right = target.right;
}
}
}
//返回parent后继节点的值,并删除该后继节点
public int deleteRightTreeMin(Node parent) {
Node target = parent.right;
if (target.left != null) {//parent的右子节点有左子节点
parent = parent.right;
target = target.left;
while (target.left != null) {//循环的查找左节点,直到找到最小值
parent = parent.left;
target = target.left;
}
parent.left = target.right;//删除该最小值节点,此时target没有左右子节点或只有右节点
} else {//target.left == null,parent的右子节点没有有左子节点
parent.right = target.right;//删除该最小值节点,此时target没有左右子节点或只有右节点
}
return target.value;
}
//返回parent前驱节点的值,并删除该前驱节点
public int deleteLeftTreeMax(Node parent) {
Node target = parent.left;
if (target.right != null) {//parent的左子节点有右子节点
parent = parent.left;
target = target.right;
while (target.right != null) {//循环的查找右节点 直到找到最大值
parent = parent.right;
target = target.right;
}
parent.right = target.left;//删除该最大值节点,此时target没有左右子节点或只有左节点
}else{//target.right == null,parent的左子节点没有有右子节点
parent.left = target.left;//删除该最大值节点,此时target没有左右子节点或只有左节点
}
return target.value;
}
}
class Node {
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//添加节点
public void add(Node node) {
if (node == null) {
return;
}
if (node.value < this.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else if (node.value > this.value){
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
} else {//若是相同节点,则不变
return;
}
}
//查找节点,value:节点的值;找到则返回该节点,否则返回null
public Node search(int value) {
if (value == this.value) {//找到该节点
return this;
} else if (value < this.value) {
if (this.left == null) {
return null;
} else {
return this.left.search(value);//向左子树递归查找
}
} else {//value >= this.value
if (this.right == null) {
return null;
} else {
return this.right.search(value);//向右子树递归查找
}
}
}
//查找节点的父节点,value:节点的值;找到返回节点的父节点,否则返回null
public Node searchParent(int value) {
if (this.left != null && this.left.value == value || this.right != null && this.right.value == value) {
return this;//当前节点即为要删除节点的父节点
} else {
if (value < this.value && this.left != null) {
return this.left.searchParent(value);//递归的向左子树查找
} else if (value >= this.value && this.right != null) {
return this.right.searchParent(value);//递归的向右子树查找
} else {
return null;//没有父节点
}
}
}
}
在由 N 个随机键构造的二叉查找树中
1、查找命中平均所需的比较次数为 2 * lnN(约 1.39 * lgN)
2、插入操作和查找未命中平均所需的比较次数为 2 * lnN(约 1.39 * lgN)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战