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

173 阅读3分钟

思考

  • 在 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、520it.com/binarytrees…

2、btv.melezinek.cz/binary-sear…

3、www.cs.usfca.edu/~galles/vis…

4、yangez.github.io/btree-js

代码链接