数据结构之二叉查找树
定义:二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
如图就是一颗二叉查找树:
根节点为8,根节点的左子树都比8小,根节点的右子树都比8大。
如果将二叉查找树投影到一条直线上,保证一个节点的左子树出现在它的左边,右子树出现在它的右边,那么可以得到一条有序的数列。
例如图中的二叉查找树投影后即为:1、3、4、6、7、8、10、13、14
上面的有序序列也是中序遍历的结果,
基本实现:
我们定义每个节点都有一个键、一个值、一条左链接、一条右链接、和一个节点计数器。
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;
/**
* @author yuan
* @date 2019/2/26
* @description 二叉查找树
*/
public class BST<Key extends Comparable, Value> {
private Node root;
private class Node{
private Key key;
private Value val;
private Node left, right;
/**
* 以该节点为根的子树中的节点总数
*/
private int n;
public Node(Key key, Value val, int n) {
this.key = key;
this.val = val;
this.n = n;
}
}
public int size(){
return size(root);
}
private int size(Node x) {
if (x == null) {
return 0;
}
return x.n;
}
public Value get(Key key) {
return get(root, key);
}
private Value get(Node x, Key key) {
if (key == null) {
throw new IllegalArgumentException("calls get() with a null key");
}
if (x == null) {
return null;
}
int cmp = key.compareTo(x.key);
if (cmp < 0) {
return get(x.left, key);
} else if (cmp > 0) {
return get(x.right, key);
} else {
return x.val;
}
}
public void put(Key key, Value val) {
if (key == null) {
throw new IllegalArgumentException("calls put() with a null key");
}
if (val == null) {
delete(key);
return;
}
root = put(root, key, val);
}
private Node put(Node x, Key key, Value val) {
if (x == null) {
// 如果key不存在,新建节点
return new Node(key, val, 1);
}
int cmd = key.compareTo(x.key);
if (cmd < 0) {
// 如果被查找的键小于根节点,在左子树继续插入该键
x.left = put(x.left, key, val);
} else if (cmd > 0) {
// 如果被查找的键大于于根节点,在左子树继续插入该键
x.right = put(x.right, key, val);
} else {
// 如果key存在,则更新
x.val = val;
}
x.n = size(x.left) + size(x.right) + 1;
return x;
}
public boolean isEmpty(){
return size() == 0;
}
public Key min(){
if (isEmpty()) {
throw new NoSuchElementException("calls min() with empty symbol table");
}
return min(root).key;
}
private Node min(Node x) {
if (x.left == null) {
return x;
}
return min(x.left);
}
public Key max(){
if (isEmpty()) {
throw new NoSuchElementException("calls max() with empty symbol table");
}
return max(root).key;
}
private Node max(Node x) {
if (x.right == null) {
return x;
}
return max(x.right);
}
public void deleteMin(){
if (isEmpty()) {
throw new NoSuchElementException("Symbol table underflow");
}
root = deleteMin(root);
}
private Node deleteMin(Node x) {
if (x.left == null) {
return x.right;
}
x.left = deleteMin(x.left);
x.n = size(x.left) + size(x.right) + 1;
return x;
}
public void deleteMax(){
if (isEmpty()) {
throw new NoSuchElementException("Symbol table underflow");
}
root = deleteMax(root);
}
private Node deleteMax(Node x) {
if (x.right == null) {
return x.left;
}
x.right = deleteMax(x.right);
x.n = size(x.left) + size(x.right) + 1;
return x;
}
private void delete(Key key) {
root = delete(root, key);
}
private Node delete(Node x, Key key) {
if (x == null) {
return null;
}
int cmp = key.compareTo(x.key);
if (cmp < 0) {
x.left = delete(x.left, key);
} else if (cmp > 0) {
x.right = delete(x.right, key);
} else {
// 找到被删除的节点,如果只有一个子节点,或者没有子节点的情况
if (x.right == null) {
return x.left;
}
if (x.left == null) {
return x.right;
}
// 有两个子节点,找后继节点
Node t = x;
x = min(t.right);
x.right = deleteMin(t.right);
x.left = t.left;
}
x.n = size(x.left) + size(x.right) + 1;
return x;
}
public Iterable<Key> keys(){
return keys(min(), max());
}
/**
* 查找在给定范围的key
* @param lo
* @param hi
* @return
*/
public Iterable<Key> keys(Key lo, Key hi) {
Queue<Key> queue = new LinkedList<>();
keys(root, queue, lo, hi);
return queue;
}
/**
* 使用中序遍历
* @param x
* @param queue
* @param lo
* @param hi
*/
private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
if (x == null) {
return;
}
int cmplo = lo.compareTo(x.key);
int cmphi = hi.compareTo(x.key);
if (cmplo < 0) {
keys(x.left, queue, lo, hi);
}
if (cmplo <= 0 && cmphi >= 0) {
// 如果在指定范围内,加入队列
queue.offer(x.key);
}
if (cmphi > 0) {
keys(x.right, queue, lo, hi);
}
}
public static void main(String[] args) {
BST<String, Integer> st = new BST<>();
st.put("A", 3);
st.put("Z", 5);
st.put("T", 8);
st.put("S", 4);
st.put("E", 1);
System.out.println();
for (String s : st.keys()) {
System.out.println(s + " " + st.get(s));
}
System.out.println(st.size());
System.out.println("max=" + st.max() + ",min=" + st.min());
}
}
分析:
二叉查找树的形状取决于键被插入的先后顺序。
在最好的情况下,一颗含N个节点的树是完全平衡的,每条空链接和根节点的距离都为~lgN。
在最坏的情况下,搜索路径可能有N个节点。
二叉查找树和快速排序其实类似,树的根节点就是快速排序的第一个切分元素(左侧的键都比它小,右侧的键都比它大)。
虽然二叉查找树性能以及足够好了,但是在最坏的情况下还是比较糟糕。
为了保证一颗二分查找树,无论怎么构造它,它的运行时间都是对数级(lgN)别的,我们需要用到另一种查找树:平衡查找树。