『数据结构与算法』二叉查找树(BST)
微信搜索:码农StayUp
主页地址:https://gozhuyinglong.github.io
源码分享:https://github.com/gozhuyinglong/blog-demos
1. 二叉查找树(Binary Search Tree)
二叉查找树又叫二叉排序树(Binary Sort Tree),或叫二叉搜索树,简称BST。BST是一种节点值之间有次序的二叉树。其特性是:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低,为$O(logN)$。用大$O$符号表示的时间复杂度:
算法 | 平均 | 最差 |
---|---|---|
空间 | $O(N)$ | $O(N)$ |
搜索 | $O(logN)$ | $O(N)$ |
插入 | $O(logN)$ | $O(N)$ |
删除 | $O(logN)$ | $O(N)$ |
2. BST的实现
二叉查找树要求所有的节点元素都能够排序,所以我们的Node
节点类需要实现Comparable
接口,树中的两个元素可以使用compareTo
方法进行比较。
我们节点中元素的类型为int
型,所以该接口泛型为Comparable<Integer>
,下面是具体实现:
2.1 节点类
- element 为数据元素
- left 为左子节点
- right 为右子节点
class Node implements Comparable<Integer> {
private final int element; // 数据元素
private Node left; // 左子树
private Node right; // 右子树
private Node(Integer element) {
this.element = element;
}
@Override
public int compareTo(Integer o) {
return o.compareTo(element);
}
}
2.2 二叉查找树类
- root 为树根,所有的操作均始于此
后面会在该类中增加其他方法,如添加、查找、删除等
class BinarySearchTree {
private Node root; // 树根
}
3. 插入节点
向二叉查找树中插入的节点总是叶子节点,插入过程如下:
- 若
root
为空,则将插入节点设为root
- 当前元素与插入元素通过
compareTo
进行比较,若插入元素值小,并且左子节点left
为空,则插入至当前节点左子节点;否则继续递归 - 若插入元素值大,且右子节点
right
为空,则插入至当前节点右子节点;否则继续递归。 - 若插入元素等于当前节点元素,则插入失败。注:也可以将其插入到右子节点,我这里为了方便直接放弃插入。
具体实现:
在BinarySearchTree
类中添加两个方法:
public boolean add(int element)
为公开方法private boolean add(Node node, int element)
为私有方法,内部递归使用
// 添加元素
public boolean add(int element) {
if (root == null) {
root = new Node(element);
return true;
}
return add(root, element);
}
// 添加元素(递归)
private boolean add(Node node, int element) {
if (node.compareTo(element) < 0) {
if (node.left == null) {
node.left = new Node(element);
return true;
} else {
return add(node.left, element);
}
} else if (node.compareTo(element) > 0) {
if (node.right == null) {
node.right = new Node(element);
return true;
} else {
return add(node.right, element);
}
} else {
return false;
}
}
4. 查找节点
通过二叉查找树查找元素,其过程如下:
- 若
root
为空,则查找失败 - 将当前元素与目标元素对比,若相等则查找成功。
- 若不相等,则继续递归查找:若目标值小于当前节点值,则查找左子树;否则,查找右子树。
具体实现:
在BinarySearchTree
类中添加两个方法:
public Node find(int element)
为公开方法private Node find(Node node, int element)
为私有方法,内部递归使用
// 查找元素
public Node find(int element) {
if (root == null) {
return null;
}
return find(root, element);
}
// 查询元素(递归)
private Node find(Node node, int element) {
if (node == null) {
return null;
}
int compareResult = node.compareTo(element);
if (compareResult < 0) {
return find(node.left, element);
} else if (compareResult > 0) {
return find(node.right, element);
} else {
return node;
}
}
5. 遍历节点
BST是一个有序二叉树,通过中序遍历可顺序输出树中节点。
中序遍历过程如下:
- 递归遍历左子节点
- 输出当前节点
- 递归遍历右子节点
具体实现:
在BinarySearchTree
类中添加两个方法:
public void orderPrint()
为公开方法private void orderPrint(Node node)
为私有方法,内部递归使用
// 遍历节点
public void orderPrint() {
orderPrint(root);
}
// 遍历节点(递归)
private void orderPrint(Node node) {
if (node == null) {
return;
}
// 递归左子节点
if (node.left != null) {
orderPrint(node.left);
}
// 输出当前节点
System.out.println(node.element);
// 递归右子节点
if (node.right != null) {
orderPrint(node.right);
}
}
6. 删除节点
删除节点最为复查,共有三种情况:
6.1 目标元素为叶子节点
叶子节点最容易删除,过程如下:
- 找到目标节点的父节点
- 判断目标节点是父节点的左子树还是右子树
- 若是左子树,将父节点的
left
设为空;否则将父节点的right
设为空
6.2 目标元素即有左子树,也有右子树
该情况删除操作最为复杂,过程如下:
- 找到目标节点的父节点
- 判断目标节点是父节点的左子树还是右子树
- 找到右子树中最小元素(叶子节点),将其赋给临时变量
minNode
,再将该元素从树中删除 - 将目标元素的属性赋予
minNode
。 - 若目标元素是父节点的左子树,将父节点的
left
设为minNode
;否则将父节点的right
设为minNode
6.3 目标元素只有左子树,或只有右子树
删除过程如下
- 找到目标节点的父节点
- 判断目标节点是父节点的左子树还是右子树
- 若是左子树,将父节点的
left
设为目标节点不为空的子树;否则将父节点的right
设为目标节点不为空的子树
具体实现
在BinarySearchTree
类中添加两个方法:
public boolean remove(int element)
为公开方法private boolean remove(Node parentNode, Node node, int element)
为私有方法,内部递归使用
// 删除节点
public boolean remove(int element) {
if (root == null) {
return false;
}
// 如果删除的元素是root
if (root.compareTo(element) == 0) {
if (root.right == null) {
root = root.left;
} else {
root.right.left = root.left;
root = root.right;
}
return true;
}
return remove(null, root, element);
}
// 删除节点(递归)
private boolean remove(Node parentNode, Node node, int element) {
if (node == null) {
return false;
}
// 先找到目标元素
int compareResult = node.compareTo(element);
if (compareResult < 0) {
return remove(node, node.left, element);
}
if (compareResult > 0) {
return remove(node, node.right, element);
}
// 找到目标元素,判断该节点是父节点的左子树还是右子树
boolean isLeftOfParent = false;
if (parentNode.left != null && parentNode.left.compareTo(element) == 0) {
isLeftOfParent = true;
}
// 删除目标元素
if (node.left == null && node.right == null) { // (1)目标元素为叶子节点,直接删除
if (isLeftOfParent) {
parentNode.left = null;
} else {
parentNode.right = null;
}
} else if (node.left != null && node.right != null) { // (2)目标元素即有左子树,也有右子树
// 找到右子树最小值(叶子节点),并将其删除
Node minNode = findMin(node.right);
remove(minNode.element);
// 将该最小值替换要删除的目标节点
minNode.left = node.left;
minNode.right = node.right;
if(isLeftOfParent) {
parentNode.left = minNode;
} else {
parentNode.right = minNode;
}
} else { // (3)目标元素只有左子树,或只有右子树
if (isLeftOfParent) {
parentNode.left = node.left != null ? node.left : node.right;
} else {
parentNode.right = node.left != null ? node.left : node.right;
}
}
return true;
}
}
7. 完整代码
该代码根据下图二叉查找树实现,其操作包括:添加、查找、遍历、删除、查询最小值、查询最大值。
public class BinarySearchTreeDemo {
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
System.out.println("----------------------添加元素");
Integer[] array = {5, 2, 7, 1, 4, 3, 7, 6, 9, 8};
for (Integer element : array) {
System.out.printf("添加元素[%s] --> %s\n", element, tree.add(element));
}
System.out.println("----------------------顺序输出(中序遍历)");
tree.orderPrint();
System.out.println("----------------------查找元素");
System.out.println(tree.find(7));
System.out.println("----------------------查找最小元素");
System.out.println(tree.findMin());
System.out.println("----------------------查找最大元素");
System.out.println(tree.findMax());
System.out.println("----------------------是否包含元素");
System.out.println("是否包含[0] --> \t" + tree.contains(0));
System.out.println("是否包含[2] --> \t" + tree.contains(2));
System.out.println("----------------------删除目标元素");
System.out.println("删除[0] --> \t" + tree.remove(0));
tree.orderPrint();
System.out.println("删除[1] --> \t" + tree.remove(1));
tree.orderPrint();
System.out.println("删除[4] --> \t" + tree.remove(4));
tree.orderPrint();
System.out.println("删除[7] --> \t" + tree.remove(7));
tree.orderPrint();
}
private static class BinarySearchTree {
private Node root; // 树根
/**
* 添加元素
*
* @param element
* @return
*/
public boolean add(int element) {
if (root == null) {
root = new Node(element);
return true;
}
return add(root, element);
}
/**
* 添加元素(递归)
*
* @param node
* @param element
* @return
*/
private boolean add(Node node, int element) {
if (node.compareTo(element) < 0) {
if (node.left == null) {
node.left = new Node(element);
return true;
} else {
return add(node.left, element);
}
} else if (node.compareTo(element) > 0) {
if (node.right == null) {
node.right = new Node(element);
return true;
} else {
return add(node.right, element);
}
} else {
return false;
}
}
/**
* 查询元素
*
* @param element
* @return
*/
public Node find(int element) {
if (root == null) {
return null;
}
return find(root, element);
}
/**
* 查询元素(递归)
*
* @param node
* @param element
* @return
*/
private Node find(Node node, int element) {
if (node == null) {
return null;
}
int compareResult = node.compareTo(element);
if (compareResult < 0) {
return find(node.left, element);
} else if (compareResult > 0) {
return find(node.right, element);
} else {
return node;
}
}
/**
* 查找最大值
*
* @return
*/
public Node findMax() {
return findMax(root);
}
/**
* 查找最大值(递归)
*
* @param node
* @return
*/
private Node findMax(Node node) {
if (node.right == null) {
return node;
}
return findMax(node.right);
}
/**
* 查找最小值
*
* @return
*/
private Node findMin() {
return findMin(root);
}
/**
* 查找最小值(递归)
*
* @param node
* @return
*/
private Node findMin(Node node) {
if (node.left == null) {
return node;
}
return findMin(node.left);
}
/**
* 顺序输出
*/
public void orderPrint() {
orderPrint(root);
}
/**
* 顺序输出(递归)
*
* @param node
*/
private void orderPrint(Node node) {
if (node == null) {
return;
}
// 递归左子节点
if (node.left != null) {
orderPrint(node.left);
}
// 输出当前节点
System.out.println(node.element);
// 递归右子节点
if (node.right != null) {
orderPrint(node.right);
}
}
/**
* 是否包含某值
*
* @param element
* @return
*/
public boolean contains(int element) {
if (find(element) == null) {
return false;
}
return true;
}
/**
* 删除目标元素
*
* @param element
* @return
*/
public boolean remove(int element) {
if (root == null) {
return false;
}
// 如果删除的元素是root
if (root.compareTo(element) == 0) {
if (root.right == null) {
root = root.left;
} else {
root.right.left = root.left;
root = root.right;
}
return true;
}
return remove(null, root, element);
}
/**
* 删除目标元素(递归),有三种情况:
* (1)目标元素为叶子节点
* (2)目标元素即有左子树,也有右子树
* (3)目标元素只有左子树,或只有右子树
*
* @param parentNode 当前节点的父节点
* @param node 当前节点(若当前节点上的元素与要删除的元素匹配,则删除当前节点)
* @param element 要删除的元素
* @return
*/
private boolean remove(Node parentNode, Node node, int element) {
if (node == null) {
return false;
}
// 先找到目标元素
int compareResult = node.compareTo(element);
if (compareResult < 0) {
return remove(node, node.left, element);
}
if (compareResult > 0) {
return remove(node, node.right, element);
}
// 找到目标元素,判断该节点是父节点的左子树还是右子树
boolean isLeftOfParent = false;
if (parentNode.left != null && parentNode.left.compareTo(element) == 0) {
isLeftOfParent = true;
}
// 删除目标元素
if (node.left == null && node.right == null) { // (1)目标元素为叶子节点,直接删除
if (isLeftOfParent) {
parentNode.left = null;
} else {
parentNode.right = null;
}
} else if (node.left != null && node.right != null) { // (2)目标元素即有左子树,也有右子树
// 找到右子树最小值(叶子节点),并将其删除
Node minNode = findMin(node.right);
remove(minNode.element);
// 将该最小值替换要删除的目标节点
minNode.left = node.left;
minNode.right = node.right;
if(isLeftOfParent) {
parentNode.left = minNode;
} else {
parentNode.right = minNode;
}
} else { // (3)目标元素只有左子树,或只有右子树
if (isLeftOfParent) {
parentNode.left = node.left != null ? node.left : node.right;
} else {
parentNode.right = node.left != null ? node.left : node.right;
}
}
return true;
}
}
private static class Node implements Comparable<Integer> {
private final Integer element; // 数据元素
private Node left; // 左子树
private Node right; // 右子树
private Node(Integer element) {
this.element = element;
}
@Override
public int compareTo(Integer o) {
return o.compareTo(element);
}
@Override
public String toString() {
return "Node{" +
"element=" + element +
'}';
}
}
}
输出结果:
----------------------添加元素
添加元素[5] --> true
添加元素[2] --> true
添加元素[7] --> true
添加元素[1] --> true
添加元素[4] --> true
添加元素[3] --> true
添加元素[7] --> false
添加元素[6] --> true
添加元素[9] --> true
添加元素[8] --> true
----------------------顺序输出(中序遍历)
1
2
3
4
5
6
7
8
9
----------------------查找元素
Node{element=7}
----------------------查找最小元素
Node{element=1}
----------------------查找最大元素
Node{element=9}
----------------------是否包含元素
是否包含[0] --> false
是否包含[2] --> true
----------------------删除目标元素
删除[0] --> false
1
2
3
4
5
6
7
8
9
删除[1] --> true
2
3
4
5
6
7
8
9
删除[4] --> true
2
3
5
6
7
8
9
删除[7] --> true
2
3
5
6
8
9
推荐阅读
- 《 数组 》
- 《 稀疏数组 》
- 《 链表(单链表、双链表、环形链表) 》
- 《 栈 》
- 《 队列 》
- 《 树 》
- 《 二叉树 》
- 《 二叉查找树(BST) 》
- 《 AVL树(平衡二叉树) 》
- 《 B树 》
- 《 散列表(哈希表) 》
『数据结构与算法』二叉查找树(BST)的更多相关文章
- Java数据结构与算法(4):二叉查找树
一.二叉查找树定义 二叉树每个节点都不能有多于两个的儿子.二叉查找树是特殊的二叉树,对于树中的每个节点X,它的左子树中的所有项的值小于X中的项,而它的右子树中所有项的值大于X中的项. 二叉查找树节点的 ...
- Java数据结构和算法(五)二叉排序树(BST)
Java数据结构和算法(五)二叉排序树(BST) 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 二叉排序树(Binary S ...
- 数据结构和算法(Golang实现)(27)查找算法-二叉查找树
二叉查找树 二叉查找树,又叫二叉排序树,二叉搜索树,是一种有特定规则的二叉树,定义如下: 它是一颗二叉树,或者是空树. 左子树所有节点的值都小于它的根节点,右子树所有节点的值都大于它的根节点. 左右子 ...
- HDU 3791 二叉搜索树 (数据结构与算法实验题 10.2 小明) BST
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=3791 中文题不说题意. 建立完二叉搜索树后进行前序遍历或者后序遍历判断是否一样就可以了. 跟这次的作业第 ...
- 【数据结构与算法Python版学习笔记】树——二叉查找树 Binary Search Tree
二叉搜索树,它是映射的另一种实现 映射抽象数据类型前面两种实现,它们分别是列表二分搜索和散列表. 操作 Map()新建一个空的映射. put(key, val)往映射中加入一个新的键-值对.如果键已经 ...
- javascript数据结构与算法-- 二叉树
javascript数据结构与算法-- 二叉树 树是计算机科学中经常用到的一种数据结构.树是一种非线性的数据结构,以分成的方式存储数据,树被用来存储具有层级关系的数据,比如文件系统的文件,树还被用来存 ...
- 第一章:javascript: 数据结构与算法
在前端工程师中,常常有一种声音,我们为什么要学数据结构与算法,没有数据结构与算法,我们一样很好的完成工作.实际上,算法是一个宽泛的概念,我们写的任何程序都可以称为算法,甚至往冰箱里放大象,也要通过开门 ...
- 数据结构与算法JS实现
行解算法题,没错,就是这么方便. 当然也可以使用 Node.js 环境来执行,具体参考Node.js官方文档即可. 二 对象和面向对象编程 js中5种数据类型,并没有定义更多的数据类型,但是运用j ...
- 查找系列合集-二叉查找树BST
一. 二叉树 1. 什么是二叉树? 在计算机科学中,二叉树是每个结点最多有两个子树的树结构. 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree). 二叉树常 ...
随机推荐
- dict 切片 间隔取值
1. 字典型d[k].d.get(k),如果键名不存在 报错.返回None 2. 可以为键设置不存在情况的下的覆盖None的返回值 3. 字符串str可以看成是list 4. 对字符串的截取通过切片实 ...
- 四. Ribbon负载均衡服务调用
1. 概述 1.1 Ribbon是什么 SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具. Ribbon是Netflix发布的开源项目,主要功能 ...
- (数据科学学习手札105)Python+Dash快速web应用开发——回调交互篇(中)
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- 「NOIP2009」最优贸易
「NOIP2009」最优贸易 「NOIP2009」最优贸易内存限制:128 MiB时间限制:1000 ms 题目描述C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市.任意 ...
- Result Maps collection already contains value for xxxMapper.BaseResultMap错误解决办法
原因分析: 这些代码因为是工具自动生成的,所以也没仔细检查.一个小小的错误,导致的. 解决办法: 1.由于使用ibatis的TempTestTableMapper.xml实现接口TempTestTab ...
- Docker运行Mysql,Redis,SpringBoot项目
Docker运行Mysql,Redis,SpringBoot项目 1.docker运行mysql 1.1拉取镜像 1.2启动容器 1.3进入容器 1.4开启mysql 1.5设置远程连接 1.6查看版 ...
- spark整合Phoenix相关案例
spark 读取Phoenix hbase table表到 DataFrame的方式 Demo1: 方式一:spark read读取各数据库的通用方式 方式二:spark.load 方式三:phoen ...
- docker --- (入门必读)
容器 容器就是一个视图隔离.资源可限制.独立文件系统的进程集合.所谓"视图隔离"就是能够看到部分进程以及具有独立的主机名等:控制资源使用率则是可以对于内存大小以及 CPU 使用个数 ...
- 谷粒商城为什么要选择后天管理用vue前后端分离,而商城页面选择Thymeleaf类?
1.最初使用jsp开发web项目 还记得刚学习Java的时候,自己开发一个web项目,那时候前后端分离还不流行,为了在页面中显示动态数据,最终的形式也就是以动态网页响应用户,Java一种技术jsp,J ...
- Java基本类型的内存分配在栈还是堆
我们都知道在Java里面new出来的对象都是在堆上分配空间存储的,但是针对基本类型却有所区别,基本类型可以分配在栈上,也可以分配在堆上,这是为什么? 在这之前,我们先看下Java的基本类型8种分别是: ...