二叉查找數
定義
一個二叉查找數(BST)是一顆二叉樹,其中每個結點都含有一個Comparable的鍵以及相關聯的值,且每個結點的鍵都大于其左子樹的任意結點的鍵,小于其右子樹的任意結點的鍵
基本實現
- 數據表示
鍵+ 值+ 左鏈接+右鏈接+結點計數器 - 查找
如果樹是空的,則查找未命中;如果查找的鍵小于當前結點的鍵,則繼續查找其左子樹;如果查找的鍵大于當前結點的鍵,則繼續查找其右子樹;如果查找的鍵等于當前結點的鍵,則查找命中。 - 插入
如果樹是空的,則返回一個含有該鍵值對的新結點。如果被查找的鍵小于根節點的鍵,則繼續在左子樹中插入該鍵,否則在右子樹中插入該鍵。(記得更新節點計數器) - 性能分析
二叉查找數的算法的運行時間依賴于樹的形狀,而樹的形狀則取決于鍵的插入順序。如果插入一個有序的鍵值對,則會出現最壞情況。
由N個隨機鍵構造的二叉查找數,查找命中和插入命中的平均所需比較次數為~2lnN(1.39lgN)。
二叉查找和插入的成本都是對數級別的(相比于二分查找的優勢)
有序性相關的方法實現
- 最大鍵和最小鍵
最小鍵:如果左鏈接為空,最小鍵就是根節點;如果左鏈接非空,最小鍵就是左子樹的最小鍵
最大鍵:如果右鏈接為空,最大鍵就是根節點;如果右鏈接非空,最大鍵就是右子樹的最大鍵 - 向上取整和向下取整
floor(key) : key等于二叉樹的根節點,返回根節點的值
2.1 key小于二叉樹的根節點,floor(key)一定在根節點的左子樹中
2.2 key大于二叉樹的根節點
2.2.1 右子樹中存在小于等于key的結點時,floor(key)在右子樹中
2.2.2 否則返回根節點 - 選擇操作
查找排名為k的鍵(樹中有k個小于它的節點),假設左子樹的結點數為t
3.1 如果t > k 在左子樹中查找排名為k的鍵
3.2 如果t < k 在右子樹中查找排名為k-t-1的鍵
3.3 如果t=k 返回根節點 - 排名
返回給定鍵在樹中的排名
4.1 如果給定的鍵小于根節點的鍵,則返回該鍵在左子樹中的排名
4.2 如果給定的鍵大于根節點的鍵,則返回該鍵在右子樹中的排名+左子樹的節點個數+1
4.3 如果給定的鍵等于根節點的鍵,則返回左子樹的節點個數 - 刪除最大值和最小值
deleteMin():如果左子樹不為空,則要刪除的節點在左子樹;如果左子樹為空,則返回該節點的右子樹 - 刪除操作
如果某個節點左右子樹都不為空,刪除節點以后,會產生兩個子樹而被刪除的節點的父節點只空出了一個鏈接。可以使用被刪除節點的后繼節點(右子樹中的最小節點)補充它的位置。
6.1 將指向即將被刪除的結點的鏈接保存為t
6.2 將x指向它的后繼結點min(t.right)
6.3 將x的右鏈接指向deleteMin(t.right)
6.4 將x的左鏈接設為t.left
代碼實現
package edu.princeton.cs.algs4.chapter3;
/**
* 使用二叉查找樹實現的符號表
* Created by tianxianhu on 2017/3/6.
*/
public class BST<Key extends Comparable<Key>, Value> {
private Node root;
private class Node{
private Key key;
private Value val;
private Node left;
private Node 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;
else return x.N;
}
/**
* 獲取指定鍵的值,從根節點開始,比較鍵值
* 小于當前鍵值則在左子樹中繼續尋找,大于則在右子樹中繼續尋找,等于則返回當前節點的值
* @param key
* @return
*/
public Value get(Key key) {
return get(root, key);
}
private Value get(Node x, Key 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;
}
}
/**
* 尋找key,找到則更新它的值,否則為它創建一個新節點,同時更新節點數量
* @param key
* @param val
* @return
*/
public Node put(Key key, Value val) {
return put(root, key, val);
}
private Node put(Node x, Key key, Value val) {
// 如果不存在,新建節點插入到字數當中
if (x == null)
return new Node(key, val, 1);
int cmp = key.compareTo(x.key);
// 不存在,則繼續在左/右子樹上尋找
if (cmp < 0) {
x.left = put(x.left, key, val);
} else if (cmp > 0) {
x.right = put(x.right, key, val);
} else {
// 存在,則更新值
x.val = val;
}
// 增加了節點,更新每個子樹的數量
x.N = size(x.left) + size(x.right) + 1;
return x;
}
public Key min() {
return min(root).key;
}
private Node min(Node x) {
if (x.left == null)
return x;
return min(x.left);
}
/**
* 將要查詢的鍵值和根節點的鍵值進行比較
* 1. 與根節點鍵值相等,返回當前節點的鍵
* 1. 小于根節點鍵值,則在左子樹中繼續尋找
* 2. 大于根節點鍵值,則在右子樹中尋找(可能在右子樹中,也可能是根節點)
* 2.1 如果右子樹中尋找返回null,則返回根節點
* 2.2 如果在右子樹中尋找到,則返回找到的節點
* @param key
* @return
*/
public Key floor(Key key) {
Node x = floor(root, key);
if (x == null)
return null;
return x.key;
}
private Node floor(Node x, Key key) {
if (x == null)
return null;
int cmp = key.compareTo(x.key);
// 相等,返回當前值
if (cmp == 0)
return x;
// 小于根節點,在左子樹當中尋找
if (cmp < 0)
return floor(x.left, key);
// 大于根節點,在右子樹中尋找
Node t = floor(x.right, key);
// 存在則返回該節點,不存在則返回根節點
if (t != null)
return t;
else
return x;
}
/**
* 查詢排名為k的鍵(0-based)
* 計算做子樹的節點數量t,與排名k進行比較
* 1. 如果t>k, 就繼續在左子樹中查找排名為k的鍵
* 2. 如果k>t, 就在右子樹中查找排名為k-t-1的鍵
* 3. 如果k=t, 就返回當前節點
* @param k
* @return
*/
public Key select(int k) {
return select(root, k).key;
}
private Node select(Node x, int k) {
if (x == null)
return null;
int t = size(x.left);
if (k < t) {
return select(x.left, k);
} else if (t < k) {
return select(x.right, k - t - 1);
} else {
return x;
}
}
/**
* 查詢鍵key的排名
* 將要查詢的鍵與當前的鍵值進行比較
* 1.如果相等,則返回根節點左子樹的節點數
* 2.小于根節點,則返回該鍵在左子樹中的排名
* 3.大于根節點,則返回size(x.left)+1+右子樹中的排名
* @param key
* @return
*/
public int rank(Key key) {
return rank(root, key);
}
private int rank(Node x, Key key) {
if (x == null)
return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0)
return rank(x.left, key);
else if (cmp > 0)
return 1 + size(x.left) + rank(x.right, key);
else
return size(x.left);
}
/**
* 1.不斷檢索左子樹,直到遇見空的左子樹
* 2.返回該節點的右鏈接
* 3.遞歸調用結束后更新節點計數器
*/
public void deleteMin() {
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 delete(Key key) {
root = delete(root, key);
}
/**
* 1.尋找到要刪除節點t的后繼節點min(t.right)
* 2.將后繼節點x.right指向deleteMin(x.right),即刪除后繼節點以后的子樹,該子樹大于后繼節點
* 3.將后繼節點x.left指向t.left
* @param x
* @param key
* @return
*/
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.left == null)
return x.right;
if (x.right == null)
return x.left;
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;
}
}