十一、二叉搜索树(Binary Search Tree)

思考

  • 在 n 个动态的整数中搜索某个整数?(查看其是否存在)
  • 假设使用动态数组存放元素,从第 0 个位置开始遍历搜索,平均时间复杂度:O(n)
  • 如果维护一个有序的动态数组,使用二分搜索,最坏时间复杂度:O(logn)
    但是添加、删除的平均时间复杂度是O(n)
  • 针对这个需求,有没有更好的方案?
    使用二叉搜索树,添加、删除、搜索的最坏时间复杂度均可优化至:O(logn)

二叉搜索树(Binary Search Tree)


二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为BST
又被称为:二叉查找树、二叉排序树

  • 任意一个节点的值都\color{#ed7d30}{大于}\color{#ed7d30}{左}子树所有节点的值
  • 任意一个节点的值都\color{#ed7d30}{小于}\color{#ed7d30}{右}子树所有节点的值
  • 它的左右子树也是一棵二叉搜索树
  • 二叉搜索树可以大大提高搜索数据的效率
  • 二叉搜索树存储的元素必须具备可比较性
    比如intdouble
    如果是自定义类型,需要指定比较方式
    不允许为null

二叉搜索树的接口设计

int size() // 元素的数量
boolean isEmpty() // 是否为空
void clear() // 清空所有元素
void add(E element) // 添加元素

void remove(E element) // 删除元素

boolean contains(E element) // 是否包含某元素

需要注意的是
对于我们现在使用的二叉树来说,它的元素没有索引的概念
为什么?因为用不上,没有用。

二叉搜索树类的定义

/**
 * 二叉搜索树
 */
public class BinarySearchTree<E> {
    
    private Node<E> root;
    private int size;

    // 元素的数量
    public int size() {
        return size;
    }
    
    // 是否为空
    public boolean isEmpty() {
        return size == 0; 
    }
    
    // 清空所有元素
    public void clear() {
        
    }
    
    // 添加元素
    public void add(E element) {
        
    }

    // 删除元素
    public void remove(E element) {
        
    }

     // 是否包含某元素
    public boolean contains(E element) {
        
    }
    
    private static class Node<E> {
        E element;
        Node<E> left;
        Node<E> right;
        Node<E> parent;
        public Node(E element, Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }
    }
}

添加节点


添加步骤

  1. 找到父节点parent
  2. 创建新节点node
  3. parent.left = node或者parent.right = node
public void add(E element) {
    elementNotNullCheck(element);
    
    // 添加第一个节点
    if(root == null) {
        root = new Node<>(element, null);
        size++;
        return;
    }
    
    // 添加的不是第一个节点
    Node<E> node = root;//找到父节点
    Node<E> parent = null;
    int cmp = 0;
    while(node != null) {
        cmp = compare(element,node.element);
        parent = node;
        if(cmp > 0) {
            node = node.right;
        }else if(cmp < 0) {
            node = node.left;
        }else {// 相等
            // 1
            return;
        }
    }
    
    // 看看插入到父节点的哪个位置
    Node<E> newNode = new Node<>(element,parent);
    if(cmp > 0) {
        parent.right = newNode;
    }else {
        parent.left = newNode;
    }
    size++;
}

遇到值相等的元素该如何处理?
建议覆盖旧的值,因为如果是对象的话,比如Person对象是通过年龄进行比较的,如果添加时有一个年龄一样但是姓名不一样的对象,添加的话,不会添加进来的,所以,这里进行覆盖操作比较合理。
因此在上面代码中的1处下面添加node.element = element;

元素的比较方案设计

上面已经实现了添加节点的逻辑,但是用到了元素的比较方法compare(E e1,E e2)还没有实现。如果节点的元素是简单的数字,还好进行比较,但是如果是对象呢?如何比较呢?

Integer data[] = {7,4,9,2,5,8,11,3};

BinarySearchTree<Person> bst1 = new BinarySearchTree<>();
for (int i = 0; i < data.length; i++) {
    bst1.add(new Person(data[i]));
}

BinarySearchTree<Person> bst2 = new BinarySearchTree<>(new PersonComparator());
for (int i = 0; i < data.length; i++) {
    bst2.add(new Person(data[i]));
}

允许外界传入一个 Comparator 自定义比较方案
从BinarySearchTree类的构造方法中传入Comparator比较器


然后在BinarySearchTree类的compare方法中实现:

这时就可以使用上面的bst2了。

如果没有传入Comparator,强制认定元素实现了Comparable接口
要想使用bst1的方式,则必须要求Person需要具有实现了Comparable接口。


如果直接在BinarySearchTree类的泛型中使用Comparable接口如上图,则外面的实现类的地方传入的对象(Person)也必须要实现Comparable接口,这样不过灵活。
现在只需要在BinarySearchTree类的compare方法中只需要将参数e1强转成Comparable接口即可:

Person.java

public class Person implements Comparable<Person>{
    private String name;
    private int age;
    
    public Person(String name,int age) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person o) {
//      if (age > o.age) return 1;
//      if (age < o.age) return -1;
//      return 0;
        return age - o.age;
    }
    
    @Override
    public String toString() {
        return name + "_" + age;
    }
}

打印二叉树

工具:CoderMJLee/BinaryTrees: Some operations for binary tree (github.com)
使用步骤:
1、在BinarySearchTree类中实现BinaryTreeInfo 接口


2、调用打印API

这个工具如果想把内容打印到文件中可以使用BinaryTrees.printString(bst)方法

推荐一些神奇的网站

1、http://520it.com/binarytrees/

2、http://btv.melezinek.cz/binary-search-tree.html

3、https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

4、https://yangez.github.io/btree-js

代码链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269