二叉树查找树
一棵二叉查找树(BST)是一棵二叉树,其中每个节点都含有一个Comparable的键(以及相关的值),且每个节点的键都大于其左子树中的任意节点的键key,而小于右子树的任意节点的键key。
节点
每个节点包含一个键、一个值、一个左链接、一个右链接、一个节点计数器N
每个节点包含结点数:
size(x) = size(x.left) + size(x.right) + 1;
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;
}
根据key获取value值(也可改为contains方法,是否包含某key)
思想:
如果树为空,则查找未命中;如果被查找的键和根节点的键相等,查找命中;否则递归的在左右子树中继续查找。被查找的键较小,选择左子树;较大选择右子树。
/**
* 根据key 获取val
*
* @param key
* @return
*/
public Value get(Key key) {
return get(root, key);
}
private Value get(Node x, Key key) {
// 以x为根节点的子树中查找,并返回key对应的值
// 如果找不到则返回null
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;
}
插入键值对
/**
* 插入键值对
*
* 注意需要修改结点计数器N
*
* @param key
* @param val
*/
public void put(Key key, Value val) {
// 查找key,找到更新它的值,否则为它创建一个新的节点
root = put(root, key, val);
}
public Node put(Node x, Key key, Value val) {
// 如果key存在于以x为根节点的子树中,则更新它的值
// 否则以key、value键值对的新节点插入到该子树中
if (x == null)
return new Node(key, val, 1); // 如果key不存在,插入新的键值对
int cmp = key.compareTo(x.key); // 查找插入位置,若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;
}
查找最大键和最小键key
分析:
找到最左或最右的节点即可
/**
* 查找最小键
*
* @return
*/
public Key min() {
return min(root).key;
}
private Node min(Node x) {
// 若为空,即根节点
// 若不为空,找到左子树的最左键
if (x.left == null)
return x;
else
return min(x.left);
}
/**
* 查找最大键
*
* @return
*/
public Key max() {
return max(root).key;
}
private Node max(Node x) {
if (x.right == null)
return x;
else
return max(x.right);
}
查找小于某key的最大键,大于某key的最小键
分析:以小于某key的最大键为例
如果给定键key小于二叉树根节点的键,那么小于key的最大键floor(key)一定在根节点的左子树中;
如果给定的键key大于二叉树的根节点,那么当根节点右子树中存在小于等于key的节点时,小于等于key的最大键floor(key)才会出现在右子树;否则根节点就floor(key)。
/**
* 查找小于key的最大键
*
* @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) // 若x的key等于key
return x;
if (cmp < 0) // 若key小于x.key,需在左子树递归查找
return floor(x.left, key);
Node t = floor(x.right, key);
if (t != null)
return t;
else
return x; // 根结点小于右子树所有值
}
/**
* 查找大于key的最小键
*/
public Key ceiling(Key key) {
Node x = ceiling(root, key);
if (x == null)
return null;
return x.key;
}
private Node ceiling(Node x, Key key) {
if (x == null)
return null;
int cmp = key.compareTo(x.key);
if (cmp == 0)
return x;
if (cmp > 0)
return ceiling(x.right, key);
Node t = ceiling(x.left, key);
if (t != null)
return t;
else
return x;
}
找出排名为k的键(树中有k个小于k的键,排名开始于0)
分析:
如果左子树中结点数t大于k,继续递归在左子树查找。
t = k,返回根结点中的键
t < k, 递归在右子树查找查找排名为(k-t-1)的键。
/**
* 找出排名为k的键 --树中有k个小于k的键
*
* 排名为k,是第k+1个
*/
// 如果左子树中结点数t大于k,继续递归在左子树查找。
// t=k,返回根结点中的键
// t<k, 递归在右子树查找查找排名为(k-t-1)的键。
public Key select(int k) {
return select(root, k).key;
}
private Node select(Node x, int k) {
// 返回排名为k的节点
if (x == null)
return null;
int t = size(x.left);
if (t > k)
return select(x.left, k);
else if (t < k)
return select(x.right, k - t - 1);
else
return x;
}
给定键,返回排名
如果给定键和根节点键相等,返回左子树中的节点总数t;
如果给定键小于根节点,返回该键在左子树中的排名(递归)
等于根节点,返回t+1(根节点)加上它在右子树中的排名
/**
* 返回给定键的排名
*/
public int rank(Key key) {
return rank(key, root);
}
private int rank(Key key, Node x) {
// 返回以x为根节点的子树中小于x.key的键的数量
if (x == null)
return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0)
return rank(key, x.left);
else if (cmp > 0)
return 1 + size(x.left) + rank(key, x.right);
else
return size(x.left);
}
删除最小键、最大键、删除
删除:
将指向即将被删除的节点的链接保存为t
将x指向它的后继节点min(t.right)
将x的右链接(原本指向一棵所有节点都大于x.key的二叉查找树)指向deleteMin(t.right), 也就是删除后所有节点仍然大于x.key的子二叉查找树
将x的左链接 设为t.left(其下所有的键都小于被删除的节点和它的后继节点)
/**
* 删除最小键
*/
public void deleteMin() {
root = deleteMin(root);
}
public 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() {
root = deleteMax(root);
}
public 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;
}
/**
* 删除
*/
public void delete(Key key) {
root = delete(root, key);
}
public 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 { // 找到key了
if (x.right == null)
return x.left;
if (x.left == null)
return x.right;
Node t = x;
x = min(t.right); // x指向后继节点:右子树的最小节点
x.right = deleteMin(t.right); // 新节点的右连接为删除了原删除了最小节点的右子树
x.left = t.left;
}
x.N = size(x.left) + size(x.right) + 1;
return x;
}
范围查找
参照中序遍历
/**
* 范围查找
*
* 使用foreach遍历所有键---对照中序遍历算法理解
*/
public Iterable<Key> keys() {
return keys(min(), max());
}
public Iterable<Key> keys(Key lo, Key hi) {
Queue<Key> queue = new Queue<>(); // 使用队列收集符合条件的键
keys(root, queue, lo, hi);
return queue;
}
private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
if (x == null)
return;
int cmplo = lo.compareTo(x.key); // [lo, hi]
int cmphi = hi.compareTo(x.key);
if (cmplo < 0)
keys(x.left, queue, lo, hi); // 递归查找根节点左子树
if (cmplo <= 0 && cmphi >= 0)
queue.enqueue(x.key); // 查找根节点
if (cmphi > 0)
keys(x.right, queue, lo, hi); // 递归查找根节点的右子树
}
遍历:先序、中序、后序
/**
* 二叉树遍历算法:先序、中序、后序
*/
// 中序遍历--左根右
public void print(Node x) {
if (x == null)
return;
print(x.left);
System.out.println(x.key);
print(x.right);
}
// 先序遍历--根左右
public void preprint(Node x) {
if (x == null)
return;
System.out.println(x.key);
preprint(x.left);
preprint(x.right);
}
// 后序遍历--左右根
public void postprint(Node x) {
if (x == null)
return;
postprint(x.left);
postprint(x.right);
System.out.println(x.key);
}
二叉树的高度
/**
* 二叉树高度
*
* 特别注意:叶子节点高度为0,根节点的深度为0
*/
public int height() {
return height(root);
}
private int height(Node x) {
if (x == null)
return -1;
return 1 + Math.max(height(x.left), height(x.right));
}
完整表示
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;
}
/**
* 根据key 获取val
*
* @param key
* @return
*/
public Value get(Key key) {
return get(root, key);
}
private Value get(Node x, Key key) {
// 以x为根节点的子树中查找,并返回key对应的值
// 如果找不到则返回null
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;
}
/**
* 插入键值对
*
* 注意需要修改结点计数器N
*
* @param key
* @param val
*/
public void put(Key key, Value val) {
// 查找key,找到更新它的值,否则为它创建一个新的节点
root = put(root, key, val);
}
public Node put(Node x, Key key, Value val) {
// 如果key存在于以x为根节点的子树中,则更新它的值
// 否则以key、value键值对的新节点插入到该子树中
if (x == null)
return new Node(key, val, 1); // 如果key不存在,插入新的键值对
int cmp = key.compareTo(x.key); // 查找插入位置,若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;
}
/**
* 查找最小键
*
* @return
*/
public Key min() {
return min(root).key;
}
private Node min(Node x) {
// 若为空,即根节点
// 若不为空,找到左子树的最左键
if (x.left == null)
return x;
else
return min(x.left);
}
/**
* 查找最大键
*
* @return
*/
public Key max() {
return max(root).key;
}
private Node max(Node x) {
if (x.right == null)
return x;
else
return max(x.right);
}
/**
* 查找小于key的最大键
*
* @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) // 若x的key等于key
return x;
if (cmp < 0) // 若key小于x.key,需在左子树递归查找
return floor(x.left, key);
Node t = floor(x.right, key);
if (t != null)
return t;
else
return x; // 根结点小于右子树所有值
}
/**
* 查找大于key的最小键
*/
public Key ceiling(Key key) {
Node x = ceiling(root, key);
if (x == null)
return null;
return x.key;
}
private Node ceiling(Node x, Key key) {
if (x == null)
return null;
int cmp = key.compareTo(x.key);
if (cmp == 0)
return x;
if (cmp > 0)
return ceiling(x.right, key);
Node t = ceiling(x.left, key);
if (t != null)
return t;
else
return x;
}
/**
* 找出排名为k的键 --树中有k个小于k的键
*
* 排名为k,是第k+1个
*/
// 如果左子树中结点数t大于k,继续递归在左子树查找。
// t=k,返回根结点中的键
// t<k, 递归在右子树查找查找排名为(k-t-1)的键。
public Key select(int k) {
return select(root, k).key;
}
private Node select(Node x, int k) {
// 返回排名为k的节点
if (x == null)
return null;
int t = size(x.left);
if (t > k)
return select(x.left, k);
else if (t < k)
return select(x.right, k - t - 1);
else
return x;
}
/**
* 返回给定键的排名
*/
public int rank(Key key) {
return rank(key, root);
}
private int rank(Key key, Node x) {
// 返回以x为根节点的子树中小于x.key的键的数量
if (x == null)
return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0)
return rank(key, x.left);
else if (cmp > 0)
return 1 + size(x.left) + rank(key, x.right);
else
return size(x.left);
}
/**
* 删除最小键
*/
public void deleteMin() {
root = deleteMin(root);
}
public 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() {
root = deleteMax(root);
}
public 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;
}
/**
* 删除
*/
public void delete(Key key) {
root = delete(root, key);
}
public 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 { // 找到key了
if (x.right == null)
return x.left;
if (x.left == null)
return x.right;
Node t = x;
x = min(t.right); // x指向后继节点:右子树的最小节点
x.right = deleteMin(t.right); // 新节点的右连接为删除了原删除了最小节点的右子树
x.left = t.left;
}
x.N = size(x.left) + size(x.right) + 1;
return x;
}
/**
* 范围查找
*
* 使用foreach遍历所有键---对照中序遍历算法理解
*/
public Iterable<Key> keys() {
return keys(min(), max());
}
public Iterable<Key> keys(Key lo, Key hi) {
Queue<Key> queue = new Queue<>(); // 使用队列收集符合条件的键
keys(root, queue, lo, hi);
return queue;
}
private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
if (x == null)
return;
int cmplo = lo.compareTo(x.key); // [lo, hi]
int cmphi = hi.compareTo(x.key);
if (cmplo < 0)
keys(x.left, queue, lo, hi); // 递归查找根节点左子树
if (cmplo <= 0 && cmphi >= 0)
queue.enqueue(x.key); // 查找根节点
if (cmphi > 0)
keys(x.right, queue, lo, hi); // 递归查找根节点的右子树
}
/**
* 二叉树遍历算法:先序、中序、后序
*/
// 中序遍历
public void print(Node x) {
if (x == null)
return;
print(x.left);
System.out.println(x.key);
print(x.right);
}
// 先序遍历
public void preprint(Node x) {
if (x == null)
return;
System.out.println(x.key);
preprint(x.left);
preprint(x.right);
}
// 后序遍历
public void postprint(Node x) {
if (x == null)
return;
postprint(x.left);
postprint(x.right);
System.out.println(x.key);
}
/**
* 二叉树高度
*
* 特别注意:叶子节点高度为0,根节点的深度为0
*/
public int height() {
return height(root);
}
private int height(Node x) {
if (x == null)
return -1;
return 1 + Math.max(height(x.left), height(x.right));
}
}
#
主要参考来源于《算法》第四版
https://github.com/aistrate/AlgorithmsSedgewick/blob/master/3-Searching/3-2-BinarySearchTrees/BST.java