什么是二叉搜索树?

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

  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. Linux中LVM逻辑卷管理

    一.简介 LVM是逻辑盘卷管理(Logical Volume Manager)的简称,它是Linux环境下对磁盘分区 进行管理的一种机制,LVM是建立在硬盘和分区之上的一个逻辑层,来提高磁盘分区管理的 ...

  2. MySQL 部署分布式架构 MyCAT (一)

    架构 环境 主机名 IP db1 192.168.31.205 db2 192.168.31.206 前期准备 开启防火墙,安装配置 mysql (db1,db2) firewall-cmd --pe ...

  3. [日常] 修复了grub引导问题

    上周遇到的神奇引导问题竟然被鬼使神差的修复好了.因为我的电脑是64位的也就是x86_64架构,并且是UEFI模式下,但是之前装的grub一直是grub-传统,并且一直是i386-pc平台也就是32位的 ...

  4. 批量bat脚本复制文件或文件夹

    主要用于在本地下,复制文件或文件夹到当前文件夹 @echo off echo 复制文件或文件夹到当前文件夹(复制文件选择[],复制文件夹选择[]) set /p num=输入选择的数字: : set ...

  5. JAVA框架中XML文件

    其实在JAVA开发中servlet配置,映射注入配置等等都可以用xml来配置 在此处的department是实体类的名字,而不是对应的数据库表的名字 数据库表的字段名=#{实体类属性名} 逆向工程生成 ...

  6. CF812C Sagheer and Nubian Market

    CF812C Sagheer and Nubian Market 洛谷评测传送门 题目描述 On his trip to Luxor and Aswan, Sagheer went to a Nubi ...

  7. JDOJ3007 铺地板I

    JDOJ3007 铺地板I https://neooj.com/oldoj/problem.php?id=3007 题目描述 有一个大小是 2 x N(1 <= N <= 105)的网格, ...

  8. 学习:反调试之IsDebuggerPresent

    前言:一个反调试IsDebuggerPresent的CreackMe IsDebuggerPresent函数的了解: IsDebuggerPresent 作用 确定调用进程是否由用户模式的调试器调试. ...

  9. NOIP模拟赛2(two)

    题目描述 Description 很久很久很久以前,方方方造了一台计算机,计算机中开始有一个数 \(0\) .方方方想要让这个数变成 \(a\) ,他打算每次选择一个整数,把计算机中当前的数按位或上这 ...

  10. Linux学习笔记-第10天 特殊的交换分区

    关键词,分区.mkswap swapon .uquota,RAID,/etc/fstab 此章开始,难度有些提升.不过还好自己有点基础.