思考
- 在 n 个动态的整数中搜索某个整数?(查看其是否存在)
- 假设使用动态数组存放元素,从第 0 个位置开始遍历搜索,平均时间复杂度:
O(n)
- 如果维护一个有序的动态数组,使用二分搜索,最坏时间复杂度:
O(logn)
但是添加、删除的平均时间复杂度是O(n)
- 针对这个需求,有没有更好的方案?
使用二叉搜索树,添加、删除、搜索的最坏时间复杂度均可优化至:O(logn)
二叉搜索树(Binary Search Tree)
二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为BST
又被称为:二叉查找树、二叉排序树
- 任意一个节点的值都其子树所有节点的值
- 任意一个节点的值都其子树所有节点的值
- 它的左右子树也是一棵二叉搜索树
- 二叉搜索树可以大大提高搜索数据的效率
- 二叉搜索树存储的元素必须具备可比较性
比如int
、double
等
如果是自定义类型,需要指定比较方式
不允许为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;
}
}
}
添加节点
添加步骤
- 找到父节点
parent
- 创建新节点
node
-
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