每日一省之——使用递归法实现二叉查找树(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(); } }
相关文章推荐
- 使用Mascot Capsule Micro3D V3 API实现透明效果
- VB6使用API实现串口通信
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- 在VB中使用INI文件实现每日一贴对话框
- JDK核心API:使用动态代理实现AOP功能
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- 使用C#实现BST树的管理
- VB使用API实现串口通讯的异常问题
- 声卡虚拟示波器简单功能-使用matlab DAQ工具箱中API实现
- C#中使用API来实现BEEP
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- 使用API实现的一个增加系统桌面,并且可以任意切换的小程序.
- Google Map API的使用,实现类似“海内存知己,天涯共饭否”
- 使用Google Maps API -添加线和实现线的单击事件,( 'clickable' GPolyline & GPolygon option)
- vba使用win32 API(GetOpenFileName )实现打开文件对话框
- 使用 WSAA API 实现既有资产的分析