定義:二叉查找樹(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)別的,我們需要用到另一種查找樹:平衡查找樹。