《恋上数据结构第1季》二叉搜索树 BST

简介: 《恋上数据结构第1季》二叉搜索树 BST
数据结构与算法笔记目录《恋上数据结构》 笔记目录

想加深 Java 基础推荐看这个Java 强化笔记目录

我的《恋上数据结构》源码(第1季 + 第2季):https://github.com/szluyu99/Data_Structure_Note

思考:在 n 个动态的整数中搜索某个整数?(查看其是否存在)

  • 假设使用动态数组存放元素,从第 0 个位置开始遍历搜索;

平均时间复杂度: $o(n)$
在这里插入图片描述

  • 如果维护一个有序的动态数组,使用二分搜索

最坏时间复杂度:$o(logn)$、但是添加、删除的平均时间复杂度是$o(n)$
在这里插入图片描述

  • 使用二叉搜索树

添加、删除、搜索的最坏时间复杂度均可优化至:$o(logn)$

二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为 BST

  • 又被称为:二叉查找树二叉排序树
  • 任意一个节点的值都大于子树所有节点的值
  • 任意一个节点的值都小于子树所有节点的值
  • 它的左右子树也是一棵二又搜索树

在这里插入图片描述
二叉搜索树可以大大提高搜索数据的效率;

二叉搜索树存储的元素必须具备可比较性

  • 比如 intdouble
  • 如果是自定义类型,需要指定比较方式
  • 不允许为 null

注意:对于我们现在使用的二叉树来说,它的元素没有索引的概念


BST 接口设计

我们实现的 二叉搜索树 需要继承前面实现的 通用二叉树
具体可以查看:《恋上数据结构第1季》二叉树代码实现
在这里插入图片描述
由于 BST 继承 BinaryTree,有的方法已经在 BinaryTree 中实现了,无需再次实现。

int size() // 元素的数量
boolean isEmpty() // 是否为空
void clear() // 清空所有元素
void add (E element) // 添加元素
void remove (E element) // 删除元素
boolean contains (E element) // 是否包含某元素

BST 基础

由于二叉搜索树的节点必须具有可比较性,元素的比较方案设计:

  • 允许外界传入一个 Comparator 自定义比较方案
  • 如果没有传入 Comparator,强制认定元素实现了 Comparable 接口
import java.util.Comparator;
/**
 * 二叉搜索树
 */
@SuppressWarnings("unchecked")
public class BST<E> extends BinaryTree<E> {
    
    // 比较器,根据传入的比较器实现 compareTo() 方法
    private Comparator<E> comparator;
    
    public BST(Comparator<E> comparator){ // 可以传一个比较器
        this.comparator = comparator;
    }
    public BST(){ // 不传比较器,相当于传入一个 null
        this(null); //
    }
    
    // 根据元素值获取节点元素
    private Node<E> node(E element){
        elementNotNullCheck(element);
        
        Node<E> node = root;
        while(node != null){
            int cmp = compareTo(element, node.element);
            if(cmp < 0){
                node = node.left;
            }else if (cmp > 0){
                node = node.right;
            }else{ // cmp == 0
                return node;
            }
        }
        return null;
    }
    
    /**
     * 是否包含某元素
     */
    public boolean contains(E element) {
        return node(element) != null;
    }    

    // 节点元素比较
    private int compareTo(E e1, E e2) {
        if (comparator != null) { // 传入比较器则通过比较器比较
            return comparator.compare(e1, e2);
        }
        // 没传比较器,元素内部必须自行实现了 Comparable 接口
        return ((Comparable<E>)e1).compareTo(e2);
    }
    
    // 检测传入的节点是否为空
    private void elementNotNullCheck(E element) {
        if (element == null) { // 不能传入空节点
            throw new IllegalArgumentException("element must not null");
        }
    }
    
}

添加元素: add()

在这里插入图片描述

/**
 * 添加元素
 */
public void add(E element) {
    elementNotNullCheck(element); // 不能传入空节点

    // 传入第一个节点, 若根节点为null, 则该节点为根节点
    if (root == null) {
        root = new Node<>(element, null);
        size++;
        return;
    }

    // 添加的不是第一个节点, 找到父节点
    Node<E> node = root;
    Node<E> parent = root;
    int cmp = 0;
    while (node != null) {
        parent = node; // 父节点
        // 比较【传入节点的元素值】与【父节点的元素值】
        cmp = compareTo(element, node.element);
        if (cmp > 0) { // 传入节点比父节点要大, 往右
            node = node.right;
        } else if (cmp < 0) { // 传入节点比父节点要小, 往左
            node = node.left;
        } else { // 相等, 最好是覆盖掉, 也可以采取其他操作, 看业务需求
            node.element = element;
            return;
        }
    }

    // 上面只是找到了要添加位置的父节点, 下面要将元素添加进去
    Node<E> newNode = new Node<>(element, parent);
    if (cmp > 0) {
        parent.right = newNode;
    } else {
        parent.left = newNode;
    }
    size++;
}

删除元素: remove()

删除节点 – 叶子节点

叶子节点直接删除即可;
在这里插入图片描述

删除节点 – 度为1的节点

删除度为1的节点:用子节点替代原节点的位置;

  • 如果要删除的节点不是根节点:

在这里插入图片描述

  • 如果要删除的节点是根节点:

在这里插入图片描述

删除节点 – 度为2的节点

在这里插入图片描述

/**
 * 根据传入的值删除元素
 */
public void remove(E element) {
    remove(node(element));
}
// 根据节点删除元素
private void remove(Node<E> node) {
    if (node == null) return;
    
    size--;
    
    if (node.hasTwoChildren()) { // 度为2的节点
        // 找到后继节点
        Node<E> s = successor(node);
        // 用后继节点的值覆盖度为2的节点的值
        node.element = s.element;
        // 删除后继节点
        node = s;
    }
    
    // 删除node节点(node的度必然是1或者0)
    Node<E> replacement = node.left != null ? node.left : node.right;
    
    if (replacement != null) { // node是度为1的节点
        // 更改parent
        replacement.parent = node.parent;
        // 更改parent的left、right的指向
        if (node.parent == null) { // node是度为1的节点并且是根节点
            root = replacement;
        } else if (node == node.parent.left) {
            node.parent.left = replacement;
        } else { // node == node.parent.right
            node.parent.right = replacement;
        }
    } else if (node.parent == null) { // node是叶子节点并且是根节点
        root = null;
    } else { // node是叶子节点,但不是根节点
        if (node == node.parent.left) {
            node.parent.left = null;
        } else { // node == node.parent.right
            node.parent.right = null;
        }
    }
}

// 根据元素值获取节点元素
private Node<E> node(E element){
    elementNotNullCheck(element);
    
    Node<E> node = root;
    while(node != null){
        int cmp = compareTo(element, node.element);
        if(cmp < 0){
            node = node.left;
        }else if (cmp > 0){
            node = node.right;
        }else{ // cmp == 0
            return node;
        }
    }
    return null;
}

BST 完整源码

package com.mj.tree;

import java.util.Comparator;

/**
 * 二叉搜索树
 */
@SuppressWarnings("unchecked")
public class BST<E> extends BinaryTree<E> { // 继承通用二叉树类

    // 比较器,根据传入的比较器实现 compareTo() 方法
    private Comparator<E> comparator;

    public BST(Comparator<E> comparator) { // 可以传一个比较器
        this.comparator = comparator;
    }

    public BST() { // 不传比较器,相当于传入一个 null
        this(null); //
    }

    /**
     * 是否包含某元素
     */
    public boolean contains(E element) {
        return node(element) != null;
    }

    /**
     * 添加元素
     */
    public void add(E element) {
        elementNotNullCheck(element); // 不能传入空节点

        // 传入第一个节点, 若根节点为null, 则该节点为根节点
        if (root == null) {
            root = new Node<>(element, null);
            size++;
            return;
        }

        // 添加的不是第一个节点, 找到父节点
        Node<E> node = root;
        Node<E> parent = root;
        int cmp = 0;
        while (node != null) {
            parent = node; // 父节点
            // 比较【传入节点的元素值】与【父节点的元素值】
            cmp = compareTo(element, node.element);
            if (cmp > 0) { // 传入节点比父节点要大, 往右
                node = node.right;
            } else if (cmp < 0) { // 传入节点比父节点要小, 往左
                node = node.left;
            } else { // 相等, 最好是覆盖掉, 也可以采取其他操作, 看业务需求
                node.element = element;
                return;
            }
        }

        // 上面只是找到了要添加位置的父节点, 下面要将元素添加进去
        Node<E> newNode = new Node<>(element, parent);
        if (cmp > 0) {
            parent.right = newNode;
        } else {
            parent.left = newNode;
        }
        size++;
    }

    /**
     * 根据传入的值删除元素
     */
    public void remove(E element) {
        remove(node(element));
    }
    // 根据节点删除元素
    private void remove(Node<E> node) {
        if (node == null) return;

        size--;

        if (node.hasTwoChildren()) { // 度为2的节点
            // 找到后继节点
            Node<E> s = successor(node);
            // 用后继节点的值覆盖度为2的节点的值
            node.element = s.element;
            // 删除后继节点
            node = s;
        }

        // 删除node节点(node的度必然是1或者0)
        Node<E> replacement = node.left != null ? node.left : node.right;

        if (replacement != null) { // node是度为1的节点
            // 更改parent
            replacement.parent = node.parent;
            // 更改parent的left、right的指向
            if (node.parent == null) { // node是度为1的节点并且是根节点
                root = replacement;
            } else if (node == node.parent.left) {
                node.parent.left = replacement;
            } else { // node == node.parent.right
                node.parent.right = replacement;
            }
        } else if (node.parent == null) { // node是叶子节点并且是根节点
            root = null;
        } else { // node是叶子节点,但不是根节点
            if (node == node.parent.left) {
                node.parent.left = null;
            } else { // node == node.parent.right
                node.parent.right = null;
            }
        }
    }

    // 根据元素值获取节点元素
    private Node<E> node(E element) {
        elementNotNullCheck(element);

        Node<E> node = root;
        while (node != null) {
            int cmp = compareTo(element, node.element);
            if (cmp < 0) {
                node = node.left;
            } else if (cmp > 0) {
                node = node.right;
            } else { // cmp == 0
                return node;
            }
        }
        return null;
    }

    // 节点元素比较
    private int compareTo(E e1, E e2) {
        if (comparator != null) { // 传入比较器则通过比较器比较
            return comparator.compare(e1, e2);
        }
        // 没传比较器,元素内部必须自行实现了 Comparable 接口
        return ((Comparable<E>) e1).compareTo(e2);
    }

    // 检测传入的节点是否为空
    private void elementNotNullCheck(E element) {
        if (element == null) { // 不能传入空节点
            throw new IllegalArgumentException("element must not null");
        }
    }

}

运行测试

import java.util.Comparator;

import com.mj.BinarySearchTree.Visitor;
import com.mj.file.Files;
import com.mj.printer.BinaryTrees;

public class Main {
    public static class PersonComparator implements Comparator<Person> { // 比较器
        @Override
        public int compare(Person e1, Person e2) {
            return e1.getAge() - e2.getAge();
        }
    }

    public static class PersonComparator2 implements Comparator<Person> {
        @Override
        public int compare(Person e1, Person e2) {
            return e2.getAge() - e1.getAge();
        }
    }
    
    // Integer类型的数据
    public static void test1(){
        Integer date[] = new Integer[] { 7, 4, 9, 2, 5, 8, 11, 3, 12, 1};
        BinarySearchTree<Integer> bst = new BinarySearchTree<>();
        for (int i = 0; i < date.length; i++) {
            bst.add(date[i]);
        }
        BinaryTrees.println(bst);
    }

    // Person类型的数据
    public static void test2(){
        // Java,匿名类
        BinarySearchTree<Person> bst = new BinarySearchTree<>(new Comparator<Person>() {
            @Override
            public int compare(Person e1, Person e2) {
                return e1.getAge() - e2.getAge();
            }
        });
        Integer date[] = new Integer[] { 7, 4, 9, 2, 5, 8, 11, 3, 12, 1};
        for (int i = 0; i < date.length; i++) {
            bst.add(new Person(date[i]));
        }
        BinaryTrees.println(bst);
    }
    
    // 保存打印结果
    public static void test3(){
        BinarySearchTree<Integer> bst = new BinarySearchTree<>();
        for(int i = 0; i < 40; i++){
            bst.add((int)(Math.random()*100));
        }
        String string = BinaryTrees.printString(bst);
        string += "\n";
        Files.writeToFile("F:/1.txt", string);
    }
    
    // add() 时值相等的处理
    public static void test4(){
        BinarySearchTree<Person> bst = new BinarySearchTree<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        bst.add(new Person(15, "jack"));
        bst.add(new Person(16, "rose"));
        bst.add(new Person(10, "jerry"));
        bst.add(new Person(10, "kali")); // add()时值相等最好覆盖,否则则不会替换
        BinaryTrees.println(bst);
    }
    
    // 遍历
    public static void test5(){
        Integer date[] = new Integer[] { 7, 4, 9, 2, 5, 8, 11, 3, 12, 1};
        Person persons[] = new Person[10];
        BinarySearchTree<Person> bst = new BinarySearchTree<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o2.getAge() - o1.getAge();
            }
        });
        
        for(int i = 0; i < persons.length; i++){
            persons[i] = new Person(date[i]);
            bst.add(persons[i]);
        }
        BinaryTrees.println(bst);
        System.out.print("层次遍历:");
        bst.levelOrder(new Visitor<Person>() {
            @Override
            public boolean visit(Person element) {
                System.out.print(element + " ");
                return false;
            }
            
        });
        
    }
    
    // 访问器遍历
    public static void test6(){
        Integer date[] = new Integer[] { 7, 4, 9, 2, 5, 8, 11, 3, 12, 1};
        BinarySearchTree<Integer> bst = new BinarySearchTree<>();
        for (int i = 0; i < date.length; i++) {
            bst.add(date[i]);
        }
        BinaryTrees.print(bst);
        
        System.out.print("层次遍历:");
        bst.levelOrder(new Visitor<Integer>() {
            @Override
            public boolean visit(Integer element) {
                System.out.print(element + " ");
                return false;
            }
        });
        System.out.println();
        
        System.out.print("前序遍历:");
        bst.preorder(new Visitor<Integer>() {
            public boolean visit(Integer element) {
                System.out.print(element + " ");
//                return element == 2 ? true : false;
                return false;
            }
        });
        System.out.println();
        
        System.out.print("中序遍历:");
        bst.inorder(new Visitor<Integer>() {
            @Override
            public boolean visit(Integer element) {
                System.out.print(element + " ");
                return false;
            }
        });
        System.out.println();
        
        System.out.print("后序遍历:");
        bst.postorder(null);
        bst.postorder(new Visitor<Integer>() {
            @Override
            public boolean visit(Integer element) {
                System.out.print(element + " ");
                return false;
            }
        });
        
    }
    
    // 高度
    public static void test7(){
        BinarySearchTree<Integer> bst = new BinarySearchTree<>();
        for(int i = 0; i < 20; i++){
            bst.add((int)(Math.random()*100));
        }
        BinaryTrees.print(bst);
        System.out.println();
//        System.out.println(bst.height()1);//递归求高度
        System.out.println(bst.height());
    }
    
    // 是否是完全二叉树
    public static void test8(){
        /* 
         *    7
         *  4   8
         *1   5 
         * 
         */
        BinarySearchTree<Integer> bst = new BinarySearchTree<>();
        Integer date[] = new Integer[] { 7, 4, 8, 1, 5};
        for (int i = 0; i < date.length; i++) {
            bst.add(date[i]);
        }
        BinaryTrees.println(bst);
        System.out.println(bst.isComplete());
    }

    // 是否包含某个结点
    public static void test9(){
        BinarySearchTree<Integer> bst = new BinarySearchTree<>();
        Integer date[] = new Integer[] { 7, 4, 8, 1, 5};
        for (int i = 0; i < date.length; i++) {
            bst.add(date[i]);
        }
        BinaryTrees.println(bst);
        System.out.println(bst.contains(6));
    }
    
    // 删除节点
    public static void test10(){
        BinarySearchTree<Integer> bst = new BinarySearchTree<>();
        Integer date[] = new Integer[] { 7, 4, 8, 1, 5};
        for (int i = 0; i < date.length; i++) {
            bst.add(date[i]);
        }
        BinaryTrees.println(bst);
        bst.remove(8);
        BinaryTrees.println(bst);
    }
    public static void main(String[] args) {
        // test1();
        // test2();
        // test3();
        // test4();
        // test5();
        // test6();
        // test7();
        // test8();
        // test9();
        // test10();
    }
}
相关文章
|
2天前
|
存储 Java Serverless
【数据结构】哈希表&二叉搜索树详解
本文详细介绍了二叉搜索树和哈希表这两种数据结构。二叉搜索树是一种特殊二叉树,具有左子树节点值小于根节点、右子树节点值大于根节点的特点,并且不允许键值重复。文章给出了插入、删除和搜索等方法的具体实现。哈希表则通过哈希函数将键名映射为数组下标,实现快速查找,其插入、删除和查找操作时间复杂度理想情况下为O(1)。文中还讨论了哈希函数的设计原则、哈希冲突的解决方法及哈希表的实现细节。
18 8
【数据结构】哈希表&二叉搜索树详解
|
3月前
数据结构学习记录——判断是否为同一颗二叉搜索树(题意理解、求解思路、程序搭建框架、具体函数的实现)
数据结构学习记录——判断是否为同一颗二叉搜索树(题意理解、求解思路、程序搭建框架、具体函数的实现)
25 2
|
3月前
|
机器学习/深度学习
数据结构学习记录——堆的小习题(对由同样的n个整数构成的二叉搜索树(查找树)和最小堆,下面哪个说法是不正确的)
数据结构学习记录——堆的小习题(对由同样的n个整数构成的二叉搜索树(查找树)和最小堆,下面哪个说法是不正确的)
24 1
|
2月前
|
存储
【数据结构】AVL树——平衡二叉搜索树
【数据结构】AVL树——平衡二叉搜索树
|
2月前
|
存储 Linux 数据库
【数据结构】二叉搜索树——高阶数据结构的敲门砖
【数据结构】二叉搜索树——高阶数据结构的敲门砖
|
3月前
|
存储 算法
【二叉树】数据结构——BST二叉树基本概念及算法设计(插入、删除、遍历操作)
【二叉树】数据结构——BST二叉树基本概念及算法设计(插入、删除、遍历操作)
|
3月前
|
算法
数据结构和算法学习记录——小习题-二叉树的遍历&二叉搜索树
数据结构和算法学习记录——小习题-二叉树的遍历&二叉搜索树
30 0
|
3月前
|
算法
数据结构和算法学习记录——二叉搜索树的插入操作、删除操作
数据结构和算法学习记录——二叉搜索树的插入操作、删除操作
24 0
|
9天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
11天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。