基于二叉树的二分搜索树,可以更高效对数据进行查询。
import java.util.LinkedList; import java.util.Queue; public class BST<E extends Comparable<E>>{ private class Node{ public E e; public Node left, right; public Node(E e){ this.e = e; left = null; right = null; } } private int size; private Node root; public BST(){ size = 0; root = null; } public int getSize(){ return size;} public boolean isEmpty(){ return size == 0;} // 看二分搜索树中是否包含元素e public boolean contians(E e){ return contians(root, e); } // 看以node为根的二分搜索树中是否包含元素e, 递归算法 private boolean contians(Node node, E e){ if (node == null){ return false; } if (e.compareTo(node.e) < 0){ return contians(node.left, e); } else if(e.compareTo(node.e) > 0){ return contians(node.right, e); }else{ return true; } } // 向二分搜索树中添加新的元素e public void add(E e){ root = add(root, e);} // 向以node为根的二分搜索树中插入元素e,递归算法 // 返回插入新节点后二分搜索树的根 public Node add(Node node, E e){
if (node == null){ node = new Node(e); size++; return node; }if (e.compareTo(node. e) < 0){ node. left = add(node. left , e) ; } else if (e.compareTo(node. e) > 0){ node. right = add(node. right , e) ; } return node ; } // 二分搜索树的前序遍历 public void perOrder(){ perOrder( root) ;} // 前序遍历以 node 为根的二分搜索树 , 递归算法 private void perOrder(Node node){ if (node == null){ return; } System. out.println(node. e) ; perOrder(node. left) ; perOrder(node. right) ; } // 二分搜索树的中序遍历 public void inOrder(){ inOrder( root) ;} // 中序遍历以 node 为根的二分搜索树 , 递归算法 private void inOrder(Node node){ if (node == null){ return; } inOrder(node. left) ; System. out.println(node. e) ; inOrder(node. right) ; } // 二分搜索树的后序遍历 public void postOrder(){ perOrder( root) ;} // 后序遍历以 node 为根的二分搜索树 , 递归算法 public void postOrder(Node node){ if (node == null){ return; } postOrder(node. left) ; System. out.println(node. e) ; postOrder(node. right) ; } // 二分搜索树的层序遍历 // 用了队列这一先进先出的特点,通过迭代的方法,将二分树以从上往下,从左往右的方式 // 依次打印出来 public void levelOrder(){ Queue<Node> q = new LinkedList<>() ; q.add( root) ; while (!q.isEmpty()){ Node cur = q.remove() ; System. out.println(cur. e) ; if (cur. left != null){ q.add(cur. left) ; } if (cur. right != null){ q.add(cur. right) ; } } } // 寻找二分搜索树的最小元素 public E minimum(){ if ( size == 0){ throw new IllegalArgumentException( "index is empty") ; } return minimum( root). e ; } // 返回以 node 为根的二分搜索树的最小值所在的节点 private Node minimum(Node node){ if (node. left == null){ return node ; } return minimum(node. left) ; } // 寻找二分搜索树的最大元素 public E maximum(){ if ( size == 0){ throw new IllegalArgumentException( "index is empty") ; } return maximum( root). e ; } // 返回以 node 为根的二分搜索树的最大值所在的节点 private Node maximum(Node node){ if (node. right == null){ return node ; } return maximum(node. right) ; } // 从二分搜索树中删除最小值所在节点 , 返回最小值 public E removeMin(){ E ret = minimum( root). e ; root = removeMin( root) ; return ret ; } // 删除掉以 node 为根的二分搜索树中的最小节点 // 返回删除节点后新的二分搜索树的根 // 8 8 // / / // 5 5 // / \ / \ // 2 7 4 7 // \ / // 4 3 // / // 3 // 无论最小的节点是否有右节点,我们都假设它有一个右节点 private Node removeMin(Node node){ if (node. left == null){ Node rightNode = node. right ; // 此时找到最小节点,并用 rightNode 将此最小节点的右节点存储 node. right = null; size-- ; return rightNode ; // 迭代中的 node.left 指向的最小节点已经由 node 变为 node.right } // 通过不断迭代 node 的左节点并跟新(将最小节点删除后的调整) node. left = removeMin(node. left) ; return node ; } // 从二分搜索树中删除最大值所在节点 public E removeMax(){ E ret = maximum( root). e ; root = removeMax( root) ; return ret ; } // 删除掉以 node 为根的二分搜索树中的最大节点 // 返回删除节点后新的二分搜索树的根 // 8 8 // / \ / \ // 5 15 5 15 // / \ \ / \ \ // 2 7 19 2 7 17 // \ / \ \ // 4 17 4 18 // / \ / // 3 18 3 // 同上,我们假设最大节点存在左节点 private Node removeMax(Node node){ if (node. right == null){ Node leftNode = node. left ; node. left = null; size-- ; return leftNode ; } node. right = removeMax(node. right) ; return node ; } // 从二分搜索树中删除元素为 e 的节点 public void remove( E e){ root = remove( root , e) ; } // 删除掉以 node 为根的二分搜索树中值为 e 的节点 , 递归算法 // 返回删除节点后新的二分搜索树的根 private Node remove(Node node , E e){ // 此 if 判断有两种意思: node 本身就为空,或者迭代到最后都没找到含有 e 的节点,均返回为空 if (node == null){ return null; } if (e.compareTo(node. e) < 0){ node. left = remove(node. left , e) ; return node ; } else if (e.compareTo(node. e) > 0){ node. right = remove(node. right , e) ; return node ; } else { // 找到了匹配的节点 // 待删除节点左子树为空的情况 // 8 8 // / \ / \ // 5 [15] 5 19 // / \ \ / \ / // 2 7 19 2 7 17 // \ / \ \ // 4 17 4 18 // / \ / // 3 18 3 // 跟 removeMax 是一样的方法 if (node. left == null){ Node rightNode = node. right ; node. right = null; size-- ; return rightNode ; } // 待删除节点右子树为空的情况 if (node. right == null){ Node leftNode = node. left ; node. left = null; size-- ; return leftNode ; } // 待删除节点左右子树均不为空的情况 // 找到比待删除节点大的最小节点 , 即待删除节点右子树的最小节点 // 用这个节点顶替待删除节点的位置 // 18 18 // / / // [12] 13 // / \ / \ // 7 16 7 16 // / \ / \ / \ / \ // 6 8 13 17 6 8 14 17 // \ // 14 //successor 为待删除节点右侧最小子节点 Node successor = minimum(node. right) ; // 将该节点单独拿出来 // 返回删除 successor 节点后跟新的待删除节点的右侧子节点,并储存在 successor 的有节点上 successor. right = removeMin(node. right) ; successor. left = node. left ; // 此时的 successor 节点已经拥有待删除节点的左节点和跟新过的右节点 node. left = node. right = null; return successor ; // 之所以没有 size-- 是因为调用 removeMin 的方法中含有该操作 } }}
我们再对其进行测试
public class Main { public static void main(String[] args) { BST<Integer> bst = new BST<>(); int[] arr = new int[]{18,12,12,12,12,7,3,4,8,16,13,17,14,30,20,32,50,40}; for (int i = 0; i < arr.length; i++){ bst.add(arr[i]); } // 18 // / \ // 12 30 // / \ / \ // 7 16 20 32 // / \ / \ \ // 3 8 13 17 50 // \ \ / // 4 14 40 //前序遍历 // bst.perOrder(); //中序遍历(按顺序出来) bst.inOrder(); //后续遍历 // bst.perOrder(); //层序遍历 // bst.levelOrder(); } }
前辈传授的理解前中后序遍历的经验:
比如上图的二叉树,可以将每个元素的下面画上3个点,其中两个代表着相对左右位置的点分别为左右子树
前序遍历:三个点one,two,three依次为println,left,right。先打印 18,看left,是12,此时12下面又有三个点,先看one,是打印,接着打印12,看第二个点,是left,看left,是7。以此类推,到了属性为3的节点,先打印,再看left, left没有,看right,是4,看4的下面3个点,第一个是打印,打印4,left和right都没有,返回上一层,上一层3个点都看过了,再返回上一层。是7,它的前两个点都看了,看right,是8。看8下面的3个点。。。。。。。。。。。。。。。。
依照这个思路,你就会很轻易得出这些顺序。
假设这个二叉树是完全展开,我的意思是几乎每个节点都有左右两个子节点,是一棵平衡树,它的时间复杂度等同于O(logn),但这只是理想状态,当插入一组有序的元素,根据上面add方法可以看出,它等同于一条直线,时间复杂度为O(n)。