什么是二叉搜索树?

二叉搜索树也叫做二叉排序树、二叉查找树,它有以下性质:

  1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  3. 任意节点的左、右子树也分别为二叉查找树;
  4. 没有键值相等的节点。

二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低,为O(log n)。

基本操作

1.定义BST对象

public class BST<E extends Comparable<E>> {

    /**
* 定义节点
*/
private class Node {
private E e;
private Node left;
private Node right; Node(E e) {
this.e = e;
}
} private Node root;
private int size; /**
* 获取节点数
*
* @return
*/
public int size() {
return this.size;
}
}

2.添加节点

1.采用递归,终止条件是当前节点为null时,创建新的节点;

2.如果当前节点不为null,则通过比较插入值和当前节点值的大小,递归其左/右子节点。


public void add(E element){
root = add(root, element);
} private void _add(Node node, E element){
if(node == null){
size++;
return new Node(element);
} if(element.compareTo(node.e) > 0){
node.right = _add(node.right, element);
}else if(element.compareTo(node.e) < 0){
node.left = _add(node.left, element);
} return node;
}

3.查询节点

1.通过比较当前节点值的大小,递归查找左/右子节点。


public boolean contains(E element){
return _contains(root, element);
} private boolean _contains(Node node,E element){
if(node == null) return false; if(element.compareTo(node.e) == 0){
return true;
}else if(element.compareTo(node.e) > 0){
return _contains(node.right, element);
}else{
return _contains(node.left, element);
} return false;
}

4.打印二叉树

1.通过递增树的深度dept,层级打印;

2.叶子节点通过##表示。


public String toString(){
StringBuilder builder = new StringBuilder();
_toString(root, 0, builder);
return builder.toString();
} private void _toString(Node node, int dept, StringBuilder builder){
if(int i=0; i<dept; i++){
builder.append("--");
} if(node == null){
builder.append("##\n");
return;
} builder.append(node.e).append("\n");
_toString(node.left, dept+1, builder);
_toString(right, dept+1, builder);
}

测试运行


public static void main(String[] args) { BST<Integer> bst = new BST<>(); bst.add(30);
bst.add(10);
bst.add(22);
bst.add(50);
bst.add(18);
bst.add(34);
bst.add(5); System.out.println(bst);
}

输出结果为:

30
--10
----5
------##
------##
----22
------18
--------##
--------##
------##
--50
----34
------##
------##
----##

遍历操作

二叉树的遍历分为:前序遍历、中序遍历、后序遍历,

这里的前中后指的是父节点的顺序,所以三种遍历的顺序是:

  • 前序遍历(父-左-右)
  • 中序遍历 (左-父-右)
  • 后序遍历 (左-右-父)

1.前序遍历

1.递归遍历,终止条件是当前节点为null;

2.先遍历输出父节点,再遍历左、右子节点


public void preOrder(){
StringBuilder builer = new StringBuilder();
_preOrder(root, builer);
System.out.println(builer.toString);
} private void _preOrder(Node node,StringBuilder builder){
if(node == null) return; builder.append(node.e).append(" ");
_preOrder(node.left, builder);
_preOrder(node.right, builder);
}

2.前序遍历2(非递归)

1.借助栈,首先将节点放入栈中,开始遍历栈;

2.遍历取出节点后再将其右子节点和左子节点放入压入栈中;


public void preOrder2(){
StringBuilder builder = new StringBuilder(); Stack<Node> stack = new Stack();
stack.push(root); while(!stack.isEmpty()){
Node node = stack.pop();
if(node == null) continue; builder.append(node.e).append(" ");
stack.push(node.right);
stack.push(node.left);
} System.out.println(builder.toString());
}

3.中序遍历

1.同前序遍历,只是输出的顺序不同


public void inOrder(){
StringBuilder builer = new StringBuilder();
_inOrder(root, builer);
System.out.println(builer.toString);
} private void _inOrder(Node node,StringBuilder builder){
if(node == null) return; _inOrder(node.left, builder);
builder.append(node.e).append(" ");
_inOrder(node.right, builder);
}

输入你会发现,二叉搜索树的中序遍历的结果是有序的(从小到大)。

4.后序遍历

1.同前序遍历,只是输出的顺序不同


public void postOrder(){
StringBuilder builer = new StringBuilder();
_postOrder(root, builer);
System.out.println(builer.toString);
} private void _postOrder(Node node,StringBuilder builder){
if(node == null) return; _postOrder(node.left, builder);
_postOrder(node.right, builder);
builder.append(node.e).append(" ");
}

5.层序遍历

层序遍历指的是从上到下,从左到右一层层遍历,其遍历方法和上面的前序遍历类似,只是这里借助的是队列;

1.需要借助队列,首选将根节点入队,开始遍历队列;

2.如果当前节点不为null,则将其左子节点和右子节点入队,继续遍历;


public void levelOrder() {
StringBuilder builder = new StringBuilder(); Queue<Node> queue = new LinkedList<>();
queue.offer(root); while (!queue.isEmpty()) {
Node node = queue.poll(); if (node == null) continue; builder.append(node.e).append(" "); queue.offer(node.left);
queue.offer(node.right);
} System.out.println(builder.toString());
}

删除

1.删除最小/最大节点

以删除最小为例,最小节点一定是树的最左边的那个:

1.因为删除的节点可能是遍历的当前节点,所以需要通过return的形式返回节点;

2.判断左子节点是否为null:

如果不为null则递归遍历左子节点;

如果为null则要删除的是当前节点,要把它的右节点返回;

public Node removeMinimum() {
root = _removeMinimun(root);
return root;
} private Node _removeMinimun(Node node) {
if (node == null) return null; if (node.left == null) {
//如果左子节点为null,则把右子节点返回
Node tmp = node.right;
node.right = null;
size--;
return tmp;
} node.left = _removeMinimun(node.left);
return node;
}

2.删除任意节点

分三种情况:

  • 如果该节点没有左子节点:返回其右子节点;
  • 如果该节点没有右子节点:返回其左子节点;
  • 如果该节点同时拥有左右子节点:

节点的后继节点是大于它并最接近它的那个节点,也就是该节点的右子树中最小的节点,就是其右子树中最左边的那个,如下图中15的后继节点是17。

1.需要找到其后继节点;

2.将后继节点删除;

3.将后继节点的左子树等于该节点的左子树;

4.将后继节点的右子树等于该节点的右子树删除后继节点返回的树。


/**
* 删除任意节点,并返回删除后的根节点
*
* @param e
* @return
*/
public void removeNode(E e) {
root = _removeNode(root, e);
} private Node _removeNode(Node node, E e) {
if (node == null) return null; if (e.compareTo(node.e) < 0) {
node.left = _removeNode(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = _removeNode(node.right, e);
} else {
//1.查找到对应的节点 //2.左子树为空的情况,直接把右子树返回
if (node.left == null) {
Node right = node.right;
node.right = null;
return right;
} //3.右子树为空的情况,直接把左子树放回
if (node.right == null) {
Node left = node.left;
node.left = null;
return left;
} //4.找到后继节点(右子树中最小的节点)
Node successor = _mininum(node.right);
//5.将右子树中最小的节点删除
successor.right = _removeMinimun(node.right);
//6.将节点的左子树等于后继节点的左子树
successor.left = node.left; node.left = node.right = null; //7.返回后继节点
return successor;
} return node;
} /**
* 查找最小的节点
*
* @param node
* @return
*/
private Node _mininum(Node node) {
if (node == null) return null; if (node.left != null) {
return _mininum(node.left);
} return node;
}

完整代码BST.java

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack; /**
* Binary Search Tree
*
* @param <E>
*/
public class BST<E extends Comparable<E>> { /**
* 定义节点
*/
private class Node {
private E e;
private Node left;
private Node right; Node(E e) {
this.e = e;
}
} private Node root;
private int size; /**
* 获取节点数
*
* @return
*/
public int size() {
return this.size;
} /**
* 添加节点
*
* @param element
*/
public void add(E element) { root = add(root, element);
} private Node add(Node node, E element) { if (node == null) {
size++;
return new Node(element);
} if (element.compareTo(node.e) > 0) {
node.right = add(node.right, element);
} else if (element.compareTo(node.e) < 0) {
node.left = add(node.left, element);
} return node;
} /**
* 是否含有节点
*
* @param element
* @return
*/
public boolean contains(E element) { return contains(root, element);
} private boolean contains(Node node, E element) {
if (node == null) return false; if (element.equals(node.e)) return true; if (element.compareTo(node.e) < 0) {
return contains(node.left, element);
} else {
return contains(node.right, element);
} } @Override
public String toString() {
StringBuilder builder = new StringBuilder(); toString(root, 0, builder); return builder.toString();
} private void toString(Node node, int dept, StringBuilder builder) {
for (int i = 0; i < dept; i++) {
builder.append("--");
} if (node == null) {
builder.append("##\n");
return;
} builder.append(node.e);
builder.append("\n"); toString(node.left, dept + 1, builder);
toString(node.right, dept + 1, builder);
} /**
* 前序遍历
*/
public void preOrder() {
StringBuilder builder = new StringBuilder(); preOrder(root, builder); System.out.println(builder.toString());
} private void preOrder(Node root, StringBuilder builder) {
if (root == null) return; builder.append(root.e).append(" ");
preOrder(root.left, builder);
preOrder(root.right, builder);
} /**
* 前序遍历(非递归)
*/
public void preOrderTraverse2() {
StringBuilder builder = new StringBuilder(); Stack<Node> stack = new Stack<>();
stack.push(root); while (!stack.isEmpty()) {
Node node = stack.pop();
if (node == null) continue; builder.append(node.e).append(" "); stack.push(node.right);
stack.push(node.left);
} System.out.println(builder.toString());
} /**
* 中序遍历
*/
public void inOrder() {
StringBuilder builder = new StringBuilder(); inOrder(root, builder);
System.out.println(builder.toString());
} private void inOrder(Node root, StringBuilder builder) {
if (root == null) return; inOrder(root.left, builder);
builder.append(root.e).append(" ");
inOrder(root.right, builder);
} /**
* 后序遍历
*/
public void postOrder() {
StringBuilder builder = new StringBuilder(); postOrder(root, builder);
System.out.println(builder.toString());
} private void postOrder(Node root, StringBuilder builder) {
if (root == null) return; postOrder(root.left, builder);
postOrder(root.right, builder);
builder.append(root.e).append(" ");
} /**
* 层序遍历
*/
public void levelOrder() {
StringBuilder builder = new StringBuilder(); Queue<Node> queue = new LinkedList<>();
queue.offer(root); while (!queue.isEmpty()) {
Node node = queue.poll(); if (node == null) continue; builder.append(node.e).append(" "); queue.offer(node.left);
queue.offer(node.right);
} System.out.println(builder.toString());
} /**
* 删除最小节点,返回删除后的根节点
*/
public Node removeMinimum() {
root = _removeMinimun(root);
return root;
} private Node _removeMinimun(Node node) {
if (node == null) return null; if (node.left == null) {
//如果左子节点为null,则把右子节点返回
Node tmp = node.right;
node.right = null;
size--;
return tmp;
} node.left = _removeMinimun(node.left);
return node;
} /**
* 删除最大节点,,返回删除后的根节点
*
* @return
*/
public Node removeMaximum() {
root = _removeMaxinum(root);
return root;
} private Node _removeMaxinum(Node node) {
if (node == null) return null; if (node.right == null) {
Node tmp = node.left;
node.left = null;
size--;
return tmp;
} node.right = _removeMaxinum(node.right);
return node;
} /**
* 删除任意节点,并返回删除后的根节点
*
* @param e
* @return
*/
public void removeNode(E e) {
root = _removeNode(root, e);
} private Node _removeNode(Node node, E e) {
if (node == null) return null; if (e.compareTo(node.e) < 0) {
node.left = _removeNode(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = _removeNode(node.right, e);
} else {
//1.查找到对应的节点 //2.左子树为空的情况,直接把右子树返回
if (node.left == null) {
Node right = node.right;
node.right = null;
return right;
} //3.右子树为空的情况,直接把左子树放回
if (node.right == null) {
Node left = node.left;
node.left = null;
return left;
} //4.找到后继节点(右子树中最小的节点)
Node successor = _mininum(node.right);
//5.将右子树中最小的节点删除
successor.right = _removeMinimun(node.right);
//6.将节点的左子树等于后继节点的左子树
successor.left = node.left; node.left = node.right = null; //7.返回后继节点
return successor;
} return node;
} /**
* 查找最小的节点
*
* @param node
* @return
*/
private Node _mininum(Node node) {
if (node == null) return null; if (node.left != null) {
return _mininum(node.left);
} return node;
} }

二叉搜索树(BST)基本操作的更多相关文章

  1. C++版 - 剑指offer 面试题24:二叉搜索树BST的后序遍历序列(的判断) 题解

    剑指offer 面试题24:二叉搜索树的后序遍历序列(的判断) 题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则返回true.否则返回false.假设输入的数组的任意两个 ...

  2. 数据结构-二叉搜索树(BST binary search tree)

    本文由@呆代待殆原创,转载请注明出处:http://www.cnblogs.com/coffeeSS/ 二叉搜索树简介 顾名思义,二叉搜索树是以一棵二叉树来组织的,这样的一棵树可以用一个链表数据结构来 ...

  3. 萌新笔记之二叉搜索树(BST)

    前言,以前搞过线段树,二叉树觉得也就那样= =.然后数据结构的课也没怎么听过,然后下周期中考... 本来以为今天英语考完可以好好搞ACM了,然后这个数据结构期中考感觉会丢人,还是好好学习一波. 二叉搜 ...

  4. 给定一个二叉搜索树(BST),找到树中第 K 小的节点

    问题:给定一个二叉搜索树(BST),找到树中第 K 小的节点. 出题人:阿里巴巴出题专家:文景/阿里云 CDN 资深技术专家. 考察点: 1. 基础数据结构的理解和编码能力 2.  递归使用 参考答案 ...

  5. 二叉搜索树(BST)学习笔记

    BST调了一天,最后遍历参数错了,没药救了-- 本文所有代码均使用数组+结构体,不使用指针! 前言--BFS是啥 BST 二叉搜索树是基于二叉树的一种树,一种特殊的二叉树. 二叉搜索树要么是一颗空树, ...

  6. 看动画学算法之:二叉搜索树BST

    目录 简介 BST的基本性质 BST的构建 BST的搜索 BST的插入 BST的删除 简介 树是类似于链表的数据结构,和链表的线性结构不同的是,树是具有层次结构的非线性的数据结构. 树是由很多个节点组 ...

  7. 在二叉搜索树(BST)中查找第K个大的结点之非递归实现

    一个被广泛使用的面试题: 给定一个二叉搜索树,请找出其中的第K个大的结点. PS:我第一次在面试的时候被问到这个问题而且让我直接在白纸上写的时候,直接蒙圈了,因为没有刷题准备,所以就会有伤害.(面完的 ...

  8. 二叉搜索树 (BST) 的创建以及遍历

    二叉搜索树(Binary Search Tree) : 属于二叉树,其中每个节点都含有一个可以比较的键(如需要可以在键上关联值), 且每个节点的键都大于其左子树中的任意节点而小于右子树的任意节点的键. ...

  9. [LeetCode] Convert BST to Greater Tree 将二叉搜索树BST转为较大树

    Given a Binary Search Tree (BST), convert it to a Greater Tree such that every key of the original B ...

  10. 二叉搜索树(BST)

    (第一段日常扯蛋,大家不要看)这几天就要回家了,osgearth暂时也不想弄了,毕竟不是几天就能弄出来的,所以打算过完年回来再弄.这几天闲着也是闲着,就掏出了之前买的算法导论看了看,把二叉搜索树实现了 ...

随机推荐

  1. Mixins and Python

    什么是Mixin (混入) Mixin 这个词在Python/Ruby中经常使用, Java 中几乎看不到这个名词. 在Java 中, 我们经常定一个一个子类扩展了某个基类, 同时实现某些接口. 因为 ...

  2. CF1146D Frog Jumping

    CF1146D Frog Jumping 洛谷评测传送门 题目描述 A frog is initially at position 00 on the number line. The frog ha ...

  3. [LOJ 2718][UOJ 393][BZOJ 5415][NOI 2018]归程

    [LOJ 2718][UOJ 393][BZOJ 5415][NOI 2018]归程 题意 给定一张无向图, 每条边有一个距离和一个高度. 再给定 \(q\) 组可能在线的询问, 每组询问给定一个点 ...

  4. R语言- 实验报告 - 利用R语言脚本与Java相互调用

    一. 实训内容 利用R语言对Java项目程序进行调用,本实验包括利用R语言对java的.java文件进行编译和执行输出. 在Java中调用R语言程序.本实验通过eclipse编写Java程序的方式,调 ...

  5. 与某军工背景软件企业分享Azure DevOps Server的经验

    DevOps 是英文单词Development和Operations的组合词,是一种用于促进软件开发.运维实施和质量保障部门之间协同工作的过程和方法. 结合软件开发管理规范,应用自动化的构建.测试和发 ...

  6. Windows7运行python3,提示缺少api-ms-win-crt-runtime-l1-1.0.dll

    一.实验环境 1.Windows7x64_SP1 二.操作步骤 2.1 python官网下载python3.6后,安装.运行,提示如下错误: 2.2 解决方式 去微软官网下载安装:KB2999226补 ...

  7. [MFC]_在vs2019中使用MFC快速构建简单windows窗口程序

    微软基础类库(英语: Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发 ...

  8. 三、ForkJoin分析

    ForkJoin分析 一.ForkJoin ​ ForkJoin是由JDK1.7后提供多线并发处理框架.ForkJoin的框架的基本思想是分而治之.什么是分而治之?分而治之就是将一个复杂的计算,按照设 ...

  9. Docker安装使用以及mlsql的docker安装使用说明

    1.检查内核版本,必须是3.10及以上 uname -r 2.安装 yum -y install docker #1.启动   docker systemctl start docker #1.1.验 ...

  10. Linux 搭建邮件服务器

    一.概述: 在配置邮件服务器之前,先解释几个概念. 1.SMTP:简单邮件传输协议 (Simple Mail Transfer Protocol, SMTP) 通常使用Email都很容易,但是Inte ...