06-二分搜索树 BST

Posted sout-ch233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了06-二分搜索树 BST相关的知识,希望对你有一定的参考价值。


1、树的简介

  • 树结构本身是一种天然的组织结构

    • 计算机文件夹
    技术图片 技术图片
    • 家谱
    • 图书馆图书分类
    • 公司职工
    技术图片
  • 将数据使用树结构存储后,出奇的高效

2、树的分类

  • 二分搜索树(Binary Search Tree)
  • 平衡二叉树
    • AVL
    • 红黑树
  • 并查集
  • 线段树
  • Trie (字典树,前缀树)

3、二叉树简介

技术图片
  • 一个元素具有两个"分叉"
  • 和链表一样,是动态的数据结构
class Node {
	E e;
	Node left;	//左孩子
	Node right;	//右孩子
}
  • 二叉树具有具有唯一根节点
  • 二叉树每个节点最多有两个孩子
  • 叶子节点:左右孩子都为空的节点
  • 二叉树每个节点最多有一个父亲,根节点没有父节点
技术图片
  • 二叉树具有天然递归结构
  • 每个节点的左子树也是二叉树
  • 每个节点的右子树也是二叉树
技术图片
  • 二叉树不一定是“满”的:一个节点也是二叉树;NULL也是二叉树;一个链表也可以看作是一个二叉树
技术图片

4、二分搜索树简介

  • 二分搜索树也是二叉树
  • 二分搜索树的每个节点的值:
    • 大于其左子树的所有节点的值
    • 小于其右子树的所有节点的值
  • 每一棵子树也是二分搜索树

技术图片

  • 存储的元素必须有可比较性

5、可比较性的实现

6、二分搜索树的实现

6.1、向二分搜索树中插入元素

  • 新元素,从根节点向下比较添加

技术图片

  • 重复元素,不做处理

技术图片

// 从二分搜索树中添加新的元素e
public void add(T t){
    root = add(root, t);
}
private Node add(Node node, T t){
    if(node == null){
        size++;
        return new Node(t);
    }
    if(t.compareTo(node.t) < 0){
        node.left = add(node.left, t);
    }
    else if(t.compareTo(node.t) > 0){
        node.right = add(node.right, t);
    }
    return node;
}

6.2、二分搜索树中查询元素

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

6.3、二分搜索树的遍历

  • 遍历就是把所有节点都访问一遍
  • 对于遍历操作,根节点的两棵子树都要顾及

6.3.1、递归遍历

前序遍历
//前序遍历
public void preOrder(){
    preOrder(root);
}
private void preOrder(Node node){
    if(node == null){
        return;
    }
    System.out.println(node.t);
    preOrder(node.left);
    preOrder(node.right);
}
中序遍历

中序遍历可实现二分搜索树的从小到大排序。

// 中序遍历
public void inOrder(){
    inOrder(root);
}
private void inOrder(Node node){
    if(node == null){
        return;
    }
    inOrder(node.left);
    System.out.println(node.t);
    inOrder(node.right);
}
后序遍历
public void postOrder(){
    postOrder(root);
}
private void postOrder(Node node){
    
    if(node == null){
        return;
    }
    postOrder(node.left);
    System.out.println(node.t);
    postOrder(node.right);
}

6.3.2、前序遍历的非递归实现

基于栈的实现:

  1. 首先压入根节点28,再弹出根节点28
  2. 压入弹出的节点的右孩子30、左孩子16(先入后出原则);之后弹出左孩子16,压入左孩子16的右孩子22和左孩子13;再弹出右孩子30,压入右孩子30的右孩子42和左孩子29
  3. 重复步骤二,知道栈为空为止

技术图片

//基于栈的前序遍历
public void preOrderNR(){
    
    Stack<Node> nodeStack = new ArrayStack<>();
    nodeStack.push(root);
    while (!nodeStack.isEmpty()){
        
        Node pop = nodeStack.pop();
        System.out.println(pop.t);
        if(pop.right != null){
            nodeStack.push(pop.right);
        }
        if(pop.left != null){
            nodeStack.push(pop.left);
        }
    }
}

6.3.3、广度优先遍历(层序遍历)

之前实现的的遍历方式都是深度优先优先遍历。而广度优先遍历的实现基于队列。

广度优先遍历的意义:

  • 更快地找到问题的解
  • 常用于算法设计中——最短路径
技术图片

技术图片

// 层序遍历
public void levelOrder(){

    Queue<Node> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()){
        Node remove = queue.remove();
        System.out.println(remove.t);

        if(remove.left != null){
            queue.add(remove.left);
        }
        if(remove.right != null){
            queue.add(remove.right);
        }
    }
}

6.4、删除节点

6.4.1、删除最小/最大节点

  • 最小值位于树的最左侧,最大值位于树的最右侧
技术图片
  • 最值节点不一定是叶子节点
技术图片
  • 如果最小/最大节点是叶子节点,直接删除即可;否则还需要更新移除位置的节点

技术图片

// 找到二分搜素树的最小元素
public T minimum(){
    if(size == 0){
        throw new IllegalArgumentException("树为空,删除最小值失败");
    }
    return minimum(root).t;
}
// 返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node){
    if(node.left == null){
        return node;
    }
    return minimum(node.left);
}

// 返回删除最小节点后的二分搜索树的根节点
// 删除掉以node为根的二分搜索树的最小节点
private Node removeMin(Node node) {
    if(node.left == null){
        Node rightNode = node.right;
        node.right = null;
        size--;
        return rightNode;
    }
    node.left = removeMin(node.left);
    return node;
}

// 查询二分搜素树的最大元素
public T maxmum(){
    if(size == 0){
        throw new IllegalArgumentException("树为空,删除最大值失败");
    }
    return maxmum(root).t;
}
// 返回以node为根的二分搜索树的最大值所在的节点
private Node maxmum(Node node){
    if(node.right == null){
        return node;
    }
    return maxmum(node.right);
}

public T removeMax(){
    T ret = maxmum();
    root = removeMax(root);
    return ret;
}
// 返回删除最大节点后的二分搜索树的根节点
// 删除掉以node为根的二分搜索树的最大节点
private Node removeMax(Node node) {
    if(node.right == null){
        Node leftNode = node.left;
        node.left = null;
        size--;
        return leftNode;
    }
    node.right = removeMax(node.right);
    return node;
}

6.4.2、删除指定节点

  • 删除只有左孩子的节点

技术图片

  • 删除只有右子树的节点

技术图片

  • 删除左右都有字树的节点,需要找到该节点子树中比该节点大的最小节点,使其作为新的当前节点

技术图片

6.5、全部代码

package binarySearchTree;

import java.util.LinkedList;
import java.util.Stack;
import java.util.Queue;

//二分搜索树的泛型必须具有可比较性
public class BST<T extends Comparable<T>> {

    private class Node{

        public T t;
        public Node left, right;

        public Node(T t) {

            this.t = t;
            this.left = null;
            this.right = null;
        }
    }

    private Node root;
    private int size;

    public BST() {

        root = null;
        size = 0;
    }

    public int getSize() {
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    // 从二分搜索树中添加新的元素e
    public void add(T t){
        root = add(root, t);
    }
    private Node add(Node node, T t){

        if(node == null){
            size++;
            return new Node(t);
        }
        if(t.compareTo(node.t) < 0){
            node.left = add(node.left, t);
        }
        else if(t.compareTo(node.t) > 0){
            node.right = add(node.right, t);
        }
        return node;
    }

    public boolean contains(T t){
        return contains(root, t);
    }
    private boolean contains(Node node, T t){

        if(node == null){
            return false;
        }
        if(t.compareTo(node.t) == 0){
            return true;
        }
        else if(t.compareTo(node.t) > 0){
            return contains(node.right, t);
        }
        else {
            return contains(node.left, t);
        }
    }

    //前序遍历
    public void preOrder(){
        preOrder(root);
    }
    private void preOrder(Node node){

        if(node == null){
            return;
        }

        System.out.println(node.t);
        preOrder(node.left);
        preOrder(node.right);
    }

    //基于栈的前序遍历的非递归实现
    public void preOrderNR(){

        Stack<Node> nodeStack = new Stack<>();
        nodeStack.push(root);

        while (!nodeStack.isEmpty()){

            Node pop = nodeStack.pop();
            System.out.println(pop.t);

            if(pop.right != null){
                nodeStack.push(pop.right);
            }
            if(pop.left != null){
                nodeStack.push(pop.left);
            }
        }
    }

    // 中序遍历
    public void inOrder(){
        inOrder(root);
    }
    private void inOrder(Node node){

        if(node == null){
            return;
        }

        inOrder(node.left);
        System.out.println(node.t);
        inOrder(node.right);
    }

    // 后序遍历
    public void postOrder(){
        postOrder(root);
    }
    private void postOrder(Node node){

        if(node == null){
            return;
        }

        postOrder(node.left);
        System.out.println(node.t);
        postOrder(node.right);
    }

    // 层序遍历
    public void levelOrder(){

        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()){
            Node remove = queue.remove();
            System.out.println(remove.t);

            if(remove.left != null){
                queue.add(remove.left);
            }
            if(remove.right != null){
                queue.add(remove.right);
            }
        }
    }

    // 查询而二分搜素树的最小元素
    public T minimum(){

        if(size == 0){
            throw new IllegalArgumentException("树为空,删除最小值失败");
        }
        return minimum(root).t;
    }
    // 返回以node为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){

        if(node.left == null){
            return node;
        }
        return minimum(node.left);
    }

    // 查询而二分搜素树的最大元素
    public T maxmum(){

        if(size == 0){
            throw new IllegalArgumentException("树为空,删除最大值失败");
        }
        return maxmum(root).t;
    }
    // 返回以node为根的二分搜索树的最大值所在的节点
    private Node maxmum(Node node){

        if(node.right == null){
            return node;
        }
        return maxmum(node.right);
    }

    public T removeMin(){

        T ret = minimum();
        root = removeMin(root);
        return ret;
    }
    // 返回删除最小节点后的二分搜索树的根节点
    // 删除掉以node为根的二分搜索树的最小节点
    private Node removeMin(Node node) {

        if(node.left == null){

            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    public T removeMax(){

        T ret = maxmum();
        root = removeMax(root);
        return ret;
    }
    // 返回删除最大节点后的二分搜索树的根节点
    // 删除掉以node为根的二分搜索树的最大节点
    private Node removeMax(Node node) {

        if(node.right == null){
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    public void remove(T t){
        root = remove(root, t);
    }
    private Node remove(Node node, T t) {

        if(node == null){
            return null;
        }

        if(t.compareTo(node.t) > 0){

            node.right = remove(node.right, t);
            return node;
        }
        else if(t.compareTo(node.t) < 0){

            node.left = remove(node.left, t);
            return node;
        }
        else {
            // 当前待删除节点只有右孩子的情况
            if(node.left == null){

                Node rightNode = node.right;
                node.right = null;
                size--;
                return rightNode;
            }
            // 当前待删除节点只有左孩子的情况
            if(node.right == null){

                Node leftNode = node.left;
                node.left = null;
                size--;
                return leftNode;
            }
            // 当前待删除节点左右孩子均不为空的情况
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;
            node.left = node.right = null;
            return successor;
        }
    }

    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder();
        generateBSTString(root, 0, builder);
        return builder.toString();
    }
    // 生成二分搜索树的字符串
    private void generateBSTString(Node node, int depth, StringBuilder builder){

        if(node == null){

            builder.append(generateDepthString(depth) + "null
");
            return;
        }

        builder.append(generateDepthString(depth) + node.t + "
");
        generateBSTString(node.left, depth+1, builder);
        generateBSTString(node.right, depth+1, builder);
    }
    // 生成深度信息的字符串
    private String generateDepthString(int depth) {

        StringBuilder builder = new StringBuilder();
        for (int i=0; i<depth; i++){
            builder.append("--");
        }
        return builder.toString();
    }
}

7、Java中的二分搜索树

以上是关于06-二分搜索树 BST的主要内容,如果未能解决你的问题,请参考以下文章

手撕二分搜索树java

132.Find Mode in Binary Search Tree(二分搜索树的众数)

BST二叉树的二分查找

数据结构简单谈一谈二分法和二叉排序树BST查找的比较

树--06---二叉树--03---二叉搜索树(BST)--最大深度问题折纸问题

树--04---二叉树--01---简介二叉搜索树(BST)实现