什么是二叉搜索树?

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

  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. 【XML】XML基本结构以及XML-Schema约束

    XML 简介 1998年2月,W3C正式批准了可扩展标记语言的标准定义,可扩展标记语言可以对文档和数据进行结构化处理,从而能够在部门.客户和供应商之间进行交换,实现动态内容生成,企业集成和应用开发.可 ...

  2. python async

    理解 python 的 async:1. 协程后面的技术也是 IO 多路复用, Nginx 和 nodejs 都充分利用了这种机制. 2. Flask 线程中为什么不能直接使用标准库 asyncio ...

  3. 1.JavaCC安装与测试

      下面介绍的是JavaCC在windows系统中的安装与小代码测试过程. 一.JavaCC安装   步骤 :在JavaCC的官网首页下载JavaCC,然后配置电脑的环境变量,接着在dos命令行里就可 ...

  4. rabbitmq多消费者

    rabbitmq多消费者处理 当rabbitmq拥有多个消费者时,队列收到的消息将以轮询(round-robin)的分发方式发送给消费者.每条消息只会发送给订阅列表里的一个消费者.这种方式非常适合扩展 ...

  5. [PHP] vscode配置SFTP扩展同步文件

    在我们的项目开发过程中,经常有一种模式,有一台linux的开发机作为我们的测试机器,上面安装了ftp服务.我们在windows系统的本地机器使用IDE编写代码,自动或者保存时同步上传到测试机下,这样就 ...

  6. ckeditor4.7配置图片上传

    ckeditor作为老牌的优秀在线编辑器,一直受到开发者的青睐. 这里我们讲解下 ckeditor最新版本4.7的图片上传配置. https://ckeditor.com/ 官方 进入下载 https ...

  7. react解析markdown文件

    当当当又get到了一个新技能,使用react-markdown来直接解析markdown文件(咳咳,小菜鸟的自娱自乐) 项目中遇到了一个API的那种展示方式,类似于入门手册啥的那种,如果是一个个调用接 ...

  8. Pwnable-blukat

    ssh blukat@pwnable.kr -p2222 (pw: guest) 连接上去看看c的源码 #include <stdio.h> #include <string.h&g ...

  9. jTopo介绍(一)

    jTopo(Javascript Topology library)是一款完全基于HTML5 Canvas的关系.拓扑图形化界面开发工具包.jTopo关注于数据的图形展示,它是面向开发人员的,需要进行 ...

  10. LeetCode 674. Longest Continuous Increasing Subsequence最长连续递增序列 (C++/Java)

    题目: Given an unsorted array of integers, find the length of longest continuous increasing subsequenc ...