您的位置:首页 > 其它

每日一省之——使用递归法实现二叉查找树(BST),API齐全

2016-10-02 01:28 387 查看
本来国庆该出去转转的,可是在杭州朋友不多。现在的公司规模又太大,没上一家公司凝聚力强,想找同事出来聚聚,后来发现有心无力。所以国庆还是敲代码吧。不过此刻刚写完二叉树,发现已到凌晨2点,注释也不全。改天再完善吧。之所以有勇气贴出来,是觉得对得起自己的这番苦心。并且,实现二叉搜索树一般需要实现的API我也尽可能补全了。有几个重要方法的注释其实我写的也蛮详细的,估计看方法注释也就能懂了,用不着我过多赘述。本篇虽然名为递归法实现BST,实际上非递归法的前序,中序,后序遍历也都收入其中了。若本实现有什么不对的地方,欢迎大家批评指正。

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Stack;

public class BST<K extends Comparable<K>, V> {

private Node root;

/**
* 该私有内部类用于构造二叉查找树的节点
*
* @author lhever
*/
private class Node {
private K key;
private V value;
private Node left;
private Node right;
private int N;

public Node(K key, V value, int n) {
this.key = key;
this.value = value;
this.N = n;
}

/**
* eclipse自动生成
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + N;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((left == null) ? 0 : left.hashCode());
result = prime * result + ((right == null) ? 0 : right.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}

/**
* eclipse自动生成,在这里的二叉树实现下,用于比较两个节点是否相等是完全可以的,why?大家自己思考咯
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Node other = (Node) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (N != other.N)
return false;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (left == null) {
if (other.left != null)
return false;
} else if (!left.equals(other.left))
return false;
if (right == null) {
if (other.right != null)
return false;
} else if (!right.equals(other.right))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}

private BST getOuterType() {
return BST.this;
}

}

public BST() {
}

private int size(Node node) {
if (node == null) {
return 0;
} else {
return node.N;
}

}

public boolean isEmpty() {
return size() == 0;
}

public int size() {
return size(root);
}

/**
* 查找方法, 返回与置顶键关联的值
*
* @param key
* @return
*/
public V get(K key) {
return get(root, key);
}

private V get(Node node, K key) {
if (node == null) {
return null;
}
int cmp = key.compareTo(node.key);
if (cmp < 0) {
return get(node.left, key);
} else if (cmp > 0) {
return get(node.right, key);
} else {
return node.value;
}

}

/**
* 判断是否包含key指定的键
* @param key
* @return
*/
public boolean contains(K key) {
if (key == null) {
throw new NullPointerException("参数不能为null");
}
return get(key) != null;
}

/**
* 插入键值对
* @param key
* @param value
*/
public void put(K key, V value) {
if (key == null) {
throw new NullPointerException("参数不能为null");
}
if (value == null) {
delete(key);
return;
}

root = put(root, key, value);
assert check();

}

/**
* 将指定的键值对插入二叉树中。
*
* @param node
* @param key
* @param value
* @return
*/
private Node put(Node node, K key, V value) {
if (node == null) {
return new Node(key, value, 1);
}
int cmp = key.compareTo(node.key);
if (cmp < 0) {
node.left = put(node.left, key, value);
} else if (cmp > 0) {
node.right = put(node.right, key, value);
} else {
node.value = value;
}
node.N = size(node.left) + 1 + size(node.right);
return node;
}

/**
* 查找最小的键
* @return
*/
public K min() {
if (isEmpty()) {
throw new NoSuchElementException("二叉搜索树为空");
}
return min(root).key;

}

private Node min(Node node) {
if (node.left == null) {
return node;
} else {
return min(node.left);
}
}

/**
* 查找最大的键
* @return
*/
public K max() {
if (isEmpty()) {
throw new NoSuchElementException("二叉搜索树为空");
}
return max(root).key;
}

private Node max(Node node) {
if (node.right == null) {
return node;
} else {
return max(node.right);
}
}

public void deleteMin() {
if (isEmpty()) {
throw new NoSuchElementException("二叉搜索树为空, 删除失败");
}
root = deleteMin(root);
assert check();
}

/**
* 删除以参数node为根节点的二叉搜索树的最小节点,并返回删除最小节点后的二叉搜索树。
*
* @param node
* @return
*/
private Node deleteMin(Node node) {
/*
* 左节点为空,则树中最小节点就是根节点,删除后的二叉树由根节点的右子树构成
*/
if (node.left == null) {
return node.right;
}
node.left = deleteMin(node.left);
node.N = size(node.left) + 1 + size(node.right);
return node;
}

public void deleteMax() {
if (isEmpty()) {
throw new NoSuchElementException("二叉搜索树为空, 删除失败");
}
root = deleteMax(root);
assert check();
}

/**
* 删除以参数node为根节点的二叉搜索树的最大节点,并返回删除最大节点后的二叉搜索树。
*
* @param node
* @return
*/
private Node deleteMax(Node node) {
/*
* 又节点为空,则树中最大节点就是根节点,删除后的二叉树由根节点的左子树构成
*/
if (node.right == null) {
return node.left;
}
node.right = deleteMax(node.right);
node.N = size(node.left) + 1 + size(node.right);
return node;
}

public void delete(K key) {
if (key == null) {
throw new NullPointerException("参数不能为null");
}

root = delete(root, key);
assert check();
}

private Node delete(Node node, K key) {
if (node == null) {
return null;
}
int cmp = key.compareTo(node.key);
if (cmp < 0) {
node.left = delete(node.left, key);
} else if (cmp > 0) {
node.right = delete(node.right, key);
} else {

if (node.left == null) {
return node.right;
}
;
if (node.right == null) {
return node.left;
}
Node temp = node;
node = min(temp.right);
node.right = deleteMin(temp.right);
node.left = temp.left;
}

node.N = size(node.left) + 1 + size(node.right);
return node;

}

/**
* 向下取整,这个方法的执行过程可以这样来理解(具体实现不是这样的,此处帮助理解):二叉搜索树中的键值对是键有序的, 该方法首先将树中的所有节点
* 按照键的大小排序,排好序之后,假设参数指定的key代表一个节点(该节点在树中可能存在也可能不存在),所以我们也按排序规则将参数key指定的键插入
* 之前已经排号序的序列中, 如果key的确是树中的某个节点,则这个虚拟的key与真实的某个节点重合。如果key不存在于树中,则这个虚拟的key会按大小
* 处于排好序的序列中的 某个位置。该方法返回的是小于或者等于参数指定的key,并且真实存在于树中的那个key。所以,该方法的名称与大多数编程语言中
* 提供的整数向下取整的方法语义有诸多相似之处
* @param key
* @return
*/
public K floor(K key) {
if (key == null) {
throw new NullPointerException("参数不能为null");
}
if (isEmpty()) {
throw new NoSuchElementException("二叉搜索树为空");
}
Node x = floor(root, key);
if (x == null) {
return null;
} else {
return x.key;
}
}

private Node floor(Node node, K key) {
if (node == null)
return null;
int cmp = key.compareTo(node.key);
if (cmp == 0) {
return node;
}
if (cmp < 0) {
return floor(node.left, key);
}
Node temp = floor(node.right, key);
if (temp != null) {
return temp;
} else {
return node;
}
}

/**
* 向上取整。参照floor()方法的注释来理解
* @param key
* @return
*/
public K ceiling(K key) {
if (key == null) {
throw new NullPointerException("参数不能为null");
}
if (isEmpty()) {
throw new NoSuchElementException("二叉搜索树为空");
}
Node node = ceiling(root, key);
if (node == null) {
return null;
} else {
return node.key;
}
}

private Node ceiling(Node node, K key) {
if (node == null) {
return null;
}
int cmp = key.compareTo(node.key);
if (cmp == 0) {
return node;
}
if (cmp < 0) {
Node temp = ceiling(node.left, key);
if (temp != null) {
return temp;
} else {
return node;
}
}
return ceiling(node.right, key);
}

/*
* 二叉搜索树中的键值对是键有序的, 该方法返回将树中的所有节点按键的大小排序后, 键的大小排在第k位的节点.
* 并且: 0 <= k < size() -1
*/
public K select(int k) {
if (k < 0 || k >= size()) {
throw new IllegalArgumentException();
}
Node x = select(root, k);
return x.key;
}

/**
* 二叉搜索树中的键值对是键有序的, 该方法返回将树中的所有节点按键的大小排序后, 键的大小排在第k位的节点.
*
* @param x
* @param k
* @return
*/
private Node select(Node node, int k) {
if (node == null) {
return null;
}
int t = size(node.left);
/*
* 如果当前节点的左子树的大小比k大, 则键的排名为k的节点一定在左子树中
*/
if (t > k) {
return select(node.left, k);
}
/*
* 如果当前节点的左子树大小比k小,则排名为k的节点一定在右子树中,并且在右子树中的排名为k-t-1。
* 因为k指代的是在整棵树中的排名,排名从0开始。左子树中键最大的节点排名为t-1(左子树的大小为t),当前节点排为t,
* 所以左子树加当前节点的节点总数是t+1, 排名为k的节点一定是在右子树中排名为k-(t+1)(也即 k-t-1)的节点,该节点其
* 实就是在右子树中大小为k-t-1的子树的根节点
*/
else if (t < k) {
return select(node.right, k - t - 1);
}
/*
* 因为k指代的是在整棵树中的排名,排名从0开始。左子树中键最大的节点排名为t-1(左子树的大小为t),
* 当前节点比左子树中的任何节点都大,所以恰好排名为t。所以此处返回当前节点
*/
else {
return node;
}
}

public int rank(K key) {
if (key == null) {
throw new NullPointerException("参数不能为null");
}
return rank(key, root);
}

/**
* 二叉搜索树中的键值对是键有序的, 该方法首先将树中的所有节点按键的大小排序, 然后返回参数指定的键在排好序的节点序列中的序号。
* 若键不存在于树种,一律返回0
*
* @param key
* @param node
* @return
*/
private int rank(K key, Node node) {
if (node == null) {
return 0;
}
int cmp = key.compareTo(node.key);
if (cmp < 0) {
return rank(key, node.left);
} else if (cmp > 0) {
return 1 + size(node.left) + rank(key, node.right);
} else {
return size(node.left);
}
}

public Iterable<K> keys() {
return keys(min(), max());
}

/**
* 返回键的大小在low和high两个参数指定的范围内的键
* @param low
* @param high
* @return
*/
public Iterable<K> keys(K low, K high) {
if (low == null) {
throw new NullPointerException("low为空null");
}
if (high == null) {
throw new NullPointerException("high为null");
}

Queue<K> queue = new LinkedList<K>();
keys(root, queue, low, high);
return queue;
}

private void keys(Node node, Queue<K> queue, K low, K high) {
if (node == null) {
return;
}
int cmplo = low.compareTo(node.key);
int cmphi = high.compareTo(node.key);
if (cmplo < 0) {
keys(node.left, queue, low, high);
}
if (cmplo <= 0 && cmphi >= 0) {
queue.add(node.key);
}
if (cmphi > 0) {
keys(node.right, queue, low, high);
}
}

/**
* 返回大小在low和high两个参数指定的范围内的键的总数
* @param low
* @param high
* @return
*/
public int size(K low, K high) {
if (low == null) {
throw new NullPointerException("low为null");
}
if (high == null) {
throw new NullPointerException("high为null");
}

if (low.compareTo(high) > 0) {
return 0;
}
if (contains(high)) {
return rank(high) - rank(low) + 1;
} else {
return rank(high) - rank(low);
}
}

/**
* 返回二叉搜索树的高度
*
* @return
*/
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));
}

/**
* 返回树中所有节点的迭代器,遍历所有节点的方式是层序遍历。
*
* @return
*/
public Iterable<K> levelOrder() {
Queue<K> keys = new LinkedList<K>();
Queue<Node> queue = new LinkedList<Node>();
queue.add(root);
while (!queue.isEmpty()) {
Node node = queue.remove();
if (node == null) {
continue;
}
keys.add(node.key);
queue.add(node.left);
queue.add(node.right);
}
return keys;
}

private boolean check() {
if (!isBST()) {
System.out.println("经检查不符合二叉树定义");
}
if (!checkSizeConsistent()) {
System.out.println("size检查异常");
}
if (!checkRankConsistent()) {
System.out.println("秩检查异常");
}
return isBST() && checkSizeConsistent() && checkRankConsistent();
}

/**
* 判断是否满足二叉查找树的定义(左子树的节点比当前节点都小, 右子树中的节点比当前节点都大)
*
* @return
*/
private boolean isBST() {
return isBST(root, null, null);
}

private boolean isBST(Node node, K min, K max) {
if (node == null) {
return true;
}
// 不可能比最小节点小
if (min != null && node.key.compareTo(min) <= 0) {
return false;
}
// 不可能比最大节点大
if (max != null && node.key.compareTo(max) >= 0) {
return false;
}

/*
* 切分后比较
*/
return isBST(node.left, min, node.key) && isBST(node.right, node.key, max);
}

private boolean checkSizeConsistent() {
return checkSizeConsistent(root);
}

/**
* 判断任意节点大小是不是等于: (左节点大小 + 1 + 右节点大小)
*
* @param node
* @return
*/
private boolean checkSizeConsistent(Node node) {
if (node == null) {
return true;
}
if (node.N != size(node.left) + size(node.right) + 1) {
return false;
}
return checkSizeConsistent(node.left) && checkSizeConsistent(node.right);
}

/**
* 检查各个节点的键的实际排名(秩)是否与我们通过方法计算出来的一致
*
* @return
*/
private boolean checkRankConsistent() {
for (int i = 0; i < size(); i++)
if (i != rank(select(i))) {
return false;
}
for (K key : keys()) {
if (key.compareTo(select(rank(key))) != 0)
return false;
}
return true;
}

/**
* 打印出节点的内容和值
*
* @param node
*/
public void visit(Node node) {
StringBuffer buff = new StringBuffer();
if (node != null) {
buff.append(" " + node.key + "[");
String left = node.left != null ? node.left.key + "" : "null";
String right = node.right != null ? node.right.key + "" : "null";
buff.append(left)
.append(" : ")
.append(right)
.append("] ");
}
System.out.print(buff.toString());
}

/** 递归实现前序遍历 */
private void recursivePreorder(Node node) {
if (node != null) {
visit(node);
recursivePreorder(node.left);
recursivePreorder(node.right);
}
}

public void recursivePreorder() {
recursivePreorder(root);
}

/** 递归实现中序遍历 */
private void recursiveInorder(Node node) {
if (node != null) {
recursiveInorder(node.left);
visit(node);
recursiveInorder(node.right);
}
}

public void recursiveInorder() {
recursiveInorder(root);
}

/** 递归实现后序遍历 */
private void recursivePostorder(Node node) {
if (node != null) {
recursivePostorder(node.left);
recursivePostorder(node.right);
visit(node);
}
}

public void recursivePostorder() {
recursivePostorder(root);
}

/** 非递归实现前序遍历 */
private void preorder(Node node) {
Stack<Node> stack = new Stack<Node>();
if (node != null) {
stack.push(node);
while (!stack.empty()) {
node = stack.pop();
visit(node);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
}

public void preorder() {
preorder(root);
}

/**
* 非递归实现后序遍历
*
* 后序遍历的非递归算法是三种顺序中最复杂的,原因在于,后序遍历是先访问左、右子树,再访问根节点,
* 而在非递归算法中,利用栈回退到时,并不知道是从左子树回退到根节点,还是从右子树回退到根节点,
* 如果从左子树回退到根节点,取出的根节点应该放回去,然后去访问右子树,而如果从右子树回退到根节点,此时的确轮到访问
* 根节点了。这个过程相比前序和后序,必须得在压栈时添加信息(此处的实现利用参数q记录已经访问过的节点,如果右节点已经访问过
* , 则可以访问栈中弹出的根节点,如果右节点还没有访问过,则再次将弹出的根节点压回栈中,转而访问右节点,待访问右节点结束再弹出根节点进行访问),
* 以便在退栈时可以知道是从左子树返回, 还是从右子树返回进而决定下一步的操作。
**/
private void postorder(Node p) {
Node q = p;
Stack<Node> stack = new Stack<Node>();
while (p != null) {
// 左子树入栈
for (; p.left != null; p = p.left) {
stack.push(p);
}
// 当前节点无右子或右子已经输出
while (p != null && (p.right == null || p.right == q)) {
visit(p);
q = p;// 记录上一个已输出节点
if (stack.empty()) {
return;
}
p = stack.pop();
}
// 处理右子
stack.push(p);
p = p.right;
}
}

public void postorder() {
postorder(root);
}

/** 非递归实现中序遍历 */
private void inorder(Node p) {
Stack<Node> stack = new Stack<Node>();
while (p != null) {
while (p != null) {
if (p.right != null) {
stack.push(p.right);// 当前节点右子入栈
}
stack.push(p);// 当前节点入栈
p = p.left;
}
p = stack.pop();
while (!stack.empty() && p.right == null) {
visit(p);
p = stack.pop();
}
visit(p);
if (!stack.empty()) {
p = stack.pop();

} else {
p = null;
}
}
}

public void inorder() {
inorder(root);
}

public static void main(String[] args) {
BST<String, Integer> st = new BST<String, Integer>();
st.put("China", 1);
st.put("America", 2);
st.put("Japan", 100);
st.put("Russian", 3);
st.put("Poland", 8);
st.put("India", 99);

for (String s : st.levelOrder()) {
System.out.println(s + " ---> " + st.get(s));
}

for (String s : st.keys()) {
System.out.println(s + " " + st.get(s));
}

/**
*                            H
*                           /  \
*                          /    \
*                         D      L
*                        /  \   / \
*                       /    \ I  X
*                      B      G
*                     / \    / \
*                    A   C  F  无
*                          / \
*                         E   无
*/
BST<String, String> st1 = new BST<String, String>();
st1.put("H", "H0");
st1.put("D", "D0");
st1.put("B", "B0");
st1.put("G", "G0");
st1.put("A", "A0");
st1.put("C", "C0");
st1.put("F", "F0");
st1.put("E", "E0");
st1.put("L", "L0");
st1.put("I", "I0");
st1.put("X", "X0");

st1.recursivePreorder();
System.out.println();
st1.recursiveInorder();
System.out.println();
st1.recursivePostorder();
System.out.println();
System.out.println("--------------------------------------------------------------------");
st1.preorder();
System.out.println();
st1.postorder();
System.out.println();
st1.inorder();

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息