二分搜索樹:(又:二分查找樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質:
若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
它的左、右子樹也分別爲二叉排序樹。
1.首先構建二分搜索數的基本結構
這裏使用泛型來構建,始終記住二分搜索樹存儲的元素必須有可比較性
size用來記錄二分搜索的元素個數
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 Node root; private int size; public BST() { root = null; size = 0; } public int size() { return size; } public boolean isEmpty() { return size == 0; } }
2.實現二分搜索樹的添加方法
//向二分搜索樹添加新的元素e public void add(E e) { root = add(root, e); } //向以node爲根的二分搜索樹中插入元素e,遞歸 //返回插入新節點後的二分搜索樹 private Node add(Node node, E e) { if (node == null) { size++; return new Node(e); } 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); } //向以node爲根的二分搜索樹中插入元素e,但二分搜索樹的根還是node,所以返回根節點 return node; }
3.實現二叉搜索樹的查詢方法
//判斷是否包含元素e public boolean contains(E e) { return contain(root, e); } private boolean contain(Node node, E e) { if (node == null) return false; if (e.compareTo(node.e) == 0) { return true; } else if (e.compareTo(node.e) < 0) { return contain(node.left, e); } else{ return contain(node.right, e); } }
4.實現二叉樹的前、中、後序遍歷
1.前序遍歷
//前序遍歷 public void preOrder() { preOrder(root); } //謙虛遍歷以node爲根的二分搜索樹,遞歸算法 private void preOrder(Node node) { if (node == null) return; System.out.print(node.e); preOrder(node.left); preOrder(node.right); }
2.中序遍歷
//中序遍歷 public void inOrder() { inOrder(root); } //中序結果爲從小到大 private void inOrder(Node root) { if (root == null) return; inOrder(root.left); System.out.print(root.e); inOrder(root.right); }
3.後序遍歷
//後序遍歷 public void postOrder() { postOrder(root); } private void postOrder(Node root) { if (root == null) return; postOrder(root.left); postOrder(root.right); System.out.print(root.e); }
4.測試前、中、後遍歷
這裏先是構建了一個名爲bst的二分搜索樹,然後分別進行前、中、後序輸出
有兩個System.out.println是爲了將前、中、後序分行來輸出
public class Main { public static void main(String[] args) { BST<Integer> bst = new BST<>(); int[] nums = {5,3,6,8,4,2,1}; for(int num:nums) bst.add(num); bst.preOrder(); System.out.println(); bst.inOrder(); System.out.println(); bst.postOrder(); } }
構建的二分搜索樹如下(每次添加元素都會先從root根結點開始向下比較)
測試結果:
前序:5321468
中序:1234568 注意!!! 可以發現二分搜索樹的中序遍歷結果是從小到大輸出元素的
後續:1243865
5.二叉搜索樹的層序遍歷
層序遍歷顧名思義就是一層一層的遍歷,也稱爲廣度優先遍歷
這裏藉助隊列實現層序遍歷,首先將root根節點添加到隊列中,然後移除root再將其左右子樹添加.....
public void levelOrder(){ Queue<Node> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()){ Node cur = queue.remove(); System.out.println(cur.e); if(cur.left != null) queue.add(cur.left); if(cur.right != null) queue.add(cur.right); } }
6.刪除二叉樹的最小、最大值結點
刪除二叉樹最小值結點思路:一遍遞歸尋找node,left==null; 的結點,該結點就是該二叉搜索樹的最小值結點,此時需要刪除該結點,只需將結點被賦值爲該結點的右子樹即可。
//從二分搜索樹中刪除小值所在結點,返回最小值 public E removeMin(){ //miniNode()函數返回該樹的最小值 E min = miniNode(); //removeMin()函數刪除該樹的最小值且返回刪除後最小值的根結點 root = removeMin(root); return min; } private Node removeMin(Node node) { //當node結點的左孩子爲空時,可以判斷出此時node爲此二叉搜索樹的最小值,所以刪除該結點 //使用該node結點的右子樹代替node if(node.left==null){ Node rightNode = node.right; node.right = null; size--; return rightNode; } node.left = removeMin(node.left); return node; }
刪除二叉樹最大值結點思路:一遍遞歸尋找node,right==null; 的結點,該結點就是該二叉搜索樹的最大值結點,此時需要刪除該結點,只需將結點被賦值爲該結點的左子樹即可。
public E removeMax(){ E max = maxNode(); root = removeMax(root); return max; } 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; }
7.刪除二叉搜索樹的任意結點
對於上面的第6點,只是刪除最小或最大值的節點,最小和最大值節點對於二叉搜索樹而言絕對是葉子節點。
然而對於刪除二叉搜索樹的任意節點,此時就要考慮到如果待刪節點左右孩子都不爲空的情況。
對於下面的二叉搜索樹如果要刪除的節點是58,那麼刪除之後應該是用那個節點來代替58節點的位置呢?
仔細觀察下圖會發現可以選擇58節點右子樹後節點的最小值!也就是59號節點。所以也就是找到待刪節點右子樹後最小值,將該最小值去替代待刪節點 (前提是,該待刪節點左右孩子都不爲空,如果爲空就是上面第6點講的方法了~)
//刪除以node爲根的二分搜索樹中值爲e的結點 private Node remove(Node node, E 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{ //如果該待刪節點左子樹爲空 if(node.left == null){ Node rightNode = node.right; node.right = null; size--; return rightNode; } //如果該待刪節點右子樹爲空 if(node.left == null){ Node leftNode = node.right; node.right = null; size--; return leftNode; } //待刪除節點左右子樹都不爲空 //調用miniNode函數找出node右子樹後最小的節點值 Node successor = miniNode(node.right); //調用removeMin函數刪除node右子樹後最小值的節點 successor.right = removeMin(node.right); successor.left = node.left; node.left = node.right = null; return successor; } }
如果將上面代碼看懂了後,你也許會想到對於刪除有左右孩子的節點,也可以採用待刪節點的左孩子的最大值來代替自己!
如下圖待刪節點58,你也可以選擇用該待刪節點的左子樹中的最大值,就是53來代替自己