數據結構------------------二叉查找樹(BST)的java實現
阿新 • • 發佈:2018-07-15
鏈接 oot 才會 empty rabl 輸出 nbsp ati 數量
數據結構------------------二叉查找樹(BST)的java實現
二叉查找樹(BST)是一種能夠將鏈表插入的靈活性和有序數組查找的高效性相結合的一種數據結構。它的定義如下:
二叉查找樹是一種二叉樹,它的每個節點的key都大於它左子樹中的任意節點的key小於它右子樹中的所有節點的key。
本文對二叉樹查找樹的基本功能進行了實現,包括添加元素、查找元素、刪除元素、遍歷元素等等,具體API請看下圖及後續詳細介紹:
1. 二叉樹的節點Node
二叉樹的節點包括一個鍵、值,以及左右子樹的鏈接,是BST的一個內部類。
privateclass Node { private Key key;//鍵 private Value value;//值 private Node left;//左子樹鏈接 private Node right;//右子樹鏈接 public Node(Key key, Value value, BST<Key, Value>.Node left, BST<Key, Value>.Node right) { super(); this.key = key;this.value = value; this.left = left; this.right = right; } @Override public String toString() { return "Node [key=" + key + ", value=" + value + "]"; } }
2. 添加元素
本文對二叉查找樹的添加提供了兩種實現:遞歸添加和叠代添加
/** * 遞歸添加 *@param key * @param value */ public void putByRecursion(Key key,Value value) { root = putByRecursion(root, key,value); } private Node putByRecursion(Node x, Key key, Value value) { //1. 如果x為null,返回新添加的節點 if(x == null) { return new Node(key,value,null,null); } if(key.compareTo(x.key) < 0) {//要添加的key<當前key,往其左子樹添加 x.left = putByRecursion(x.left,key,value); }else if(key.compareTo(x.key) == 0){//如果相等,替換value值 x.value = value; }else { x.right = putByRecursion(x.right,key,value);//要添加的key>當前key,往其右子樹添加 } //2. 如果不為null的返回值為當前節點 return x; } /** * 叠代添加 * @param key * @param value */ public void putByIteration(Key key,Value value) { //如果根節點為null,添加根節點 if(root == null) { root = new Node(key,value,null,null); return; } Node current = root; Node parent = null;//記錄要添加節點的父節點 while(current != null) { parent = current; if(key.compareTo(current.key) < 0) { current = current.left; }else if(key.compareTo(current.key) == 0) { current.value = value; return; }else { current = current.right; } } //判斷要添加的節點是其父節點的做節點還是右節點 if(key.compareTo(parent.key) < 0) { parent.left = new Node(key,value,null,null); }else { parent.right = new Node(key,value,null,null); } }
3. 查找
(1)返回BST的總節點數、判斷BST是否為空
//返回二叉查找樹的節點總數 public int size() { return size(root); } /** * 返回以node為根節點的子樹中的節點總數 * @param node * @return */ public int size(Node node) { if(node == null) { return 0; } return 1 + size(node.left) + size(node.right);//1 + 左子樹數量 + 右子樹數量 } /** * 判斷二叉樹是否為空 * @return */ public boolean isEmpty() { return root == null; }
(2) 查找指定Key的遞歸實現和叠代實現
public Value getByRecursive(Key key) { return getByRecursive(root,key); } /** * 遞歸實現查找 * @param x * @param key * @return */ public Value getByRecursive(Node x,Key key) { if(x == null) { return null; } if(key.compareTo(x.key) == 0) { return x.value; }else if(key.compareTo(x.key) < 0) {//查找左樹 return getByRecursive(x.left, key); }else {//查找右樹 return getByRecursive(x.right, key); } }
/** * 叠代查找(循環遍歷) * @return */ public Value getByIteration(Key key) { if(root == null) { return null; } Node current = root; while(current != null) { int cmp = key.compareTo(current.key); if(cmp < 0) {//左節點 current = current.left; }else if(cmp == 0) {//返回 return current.value; }else {//右節點 current = current.right; } } return null; }
4.遍歷
(1)前序遍歷
前序遍歷:先遍歷當前節點,之後遍歷當前節點的左子樹,最後遍歷當前節點的右子樹
/** * 前序遍歷:先打印當前節點,再打印當前節點的左左子樹和右子樹 * @param x */ public void printPreByRecursive(Node x) { if(x == null) { return; } System.out.println(x);//打印當前節點 printPreByRecursive(x.left);//遍歷左子樹 printPreByRecursive(x.right);//遍歷右子樹 } /** * 叠代前序遍歷: * 借助於一個棧數據結構 */ public void printPreByIteration() { if(root == null) { return; } LinkedList<Node> stack = new LinkedList<>(); Node current = root; while(current != null || !stack.isEmpty()) { if(current != null) {//一直遍歷左子樹,添加節點,並打印 stack.push(current); System.out.println(current); current = current.left; }else {//遇到空節點,就談一個節點,然後指向右節點 current = stack.pop(); current = current.right; } } }
(2)中序遍歷
中序遍歷:在遍歷當前節點前,先遍歷當前節點的左子樹,然後再打印當前節點,最後遍歷當前節點的右子樹
/** * 中序遍歷遞歸實現:要打印當前節點前,先打印當前節點的左子樹,再打印當前節點,最後打印當前節點的右子樹 * @param x */ public void printMidByRecursive(Node x) { if(x == null) { return; } printMidByRecursive(x.left); System.out.println(x); printMidByRecursive(x.right); } /** * 中序遍歷叠代實現: * 借助於棧數據結構 */ public void printMidByIteration() { if(root == null) { return; } Node current = root; LinkedList<Node> stack = new LinkedList<>(); while(current != null || !stack.isEmpty()) { if(current != null) { stack.push(current); current = current.left; }else { current = stack.pop(); System.out.println(current); current = current.right; } } }
(3)後序遍歷
後序遍歷:先遍歷當前節點的左右子樹,再打印當前節點:
/** * 後序遍歷:要輸出當前節點前,先輸出當前節點的左右節點 * @param x */ public void printBackByRecursive(Node x) { if(x == null) { return; } printBackByRecursive(x.left); printBackByRecursive(x.right); System.out.println(x); } /** * 後續遍歷的叠代實現 */ public void printBackByIteration() { if(root == null) { return; } LinkedList<Node> stack = new LinkedList<>(); LinkedList<Node> output = new LinkedList<>(); stack.push(root); //存放數據 while(!stack.isEmpty()) { Node current = stack.pop(); output.push(current); if(current.left != null) { stack.push(current.left); } if(current.right != null) { stack.push(current.right); } } //遍歷數據 while(!output.isEmpty()) { System.out.println(output.pop()); } }
5. 最小鍵和最大鍵的獲取
/** * 獲取最小key * @return */ public Key min() { return min(root).key; } public Node min(Node x) { if(x.left == null) { return x; } return min(x.left); } /** * 返回最大Key * @return */ public Key max() { return max(root).key; } public Node max(Node x) { if(x.right == null) { return x; } return max(x.right); }
6. 向上取整、向下取整
public Key floor(Key key) { Node node = floor(root,key); if(node != null) { return node.key; } return null; } /** * 查找小於等於指定鍵的最大鍵: * 思想: * 1.如果給定的鍵小於根節點的鍵,那麽要查找的鍵肯定在二叉樹的左側。 * 2.如果給定的鍵等於根節點的鍵,自然返回根節點的鍵 * 3.如果給定的鍵大於根節點的鍵,那麽只有當根節點的右子樹中存在小於等於key的節點時,小於等於key的最大鍵才會出現在右子樹,否則就是根節點 * @param node * @param key * @return */ public Node floor(Node node, Key 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 t = floor(node.right,key); if(t != null) { return t; }else { return node; } } public Key ceil(Key key) { Node x = ceil(root,key); if(x != null) { return x.key; } return null; } /** * 查找大於等於指定鍵的最小鍵 * 1. 如果給定的鍵大於根節點的鍵,那麽要查找的鍵肯定在根節點的右子樹 * 2. 如果給定的鍵等於根節點的鍵,那麽要查找的鍵就是該鍵 * 3. 如果給定的鍵小於等於根節點的鍵,那麽當且僅當根節點的左子樹中存在大於等於給定鍵的節點時,才會出現在左子樹中,否則就是根節點 * @param root2 * @param key * @return */ private Node ceil(Node node, Key key) { if(node == null) { return null; } int cmp = key.compareTo(node.key); if(cmp > 0) { return ceil(node.right,key); } if(cmp == 0) return node; Node x = ceil(node.left,key); if(x != null) { return x; } return null; }
7. 鍵的排名
/** * 返回排名為k的節點: * 思想:通過判斷以根節點左子樹的數量來判斷 * @param node * @param k * @return */ private Node select(Node node, int k) { if(node == null) return null; int size = size(node.left); if(size > k) { return select(node.left,k); }else if(size < k) { return select(node.right,k-size-1); }else { return node; } } public int rank(Key key) { return rank(root,key); } /** * 返回以node為根節點的子樹中小於x.key的鍵的數量 * @param node * @param key * @return */ private int rank(Node node, Key key) { if(node == null) { return 0; } int cmp = key.compareTo(node.key); if(cmp < 0) { return rank(node.left,key); }else if(cmp > 0) { return size(node.left) + 1 + rank(node.right,key); }else { return size(node.left); } }
8. 節點刪除
(1)刪除最大、最小節點
public void deleteMin() { root = deleteMin(root); } /** * 刪除最小key * * 如果node.left == null 返回node.right; * 否則,將當前節點的做節點的做節點指向delete(node.left) * @param node * @return 返回值有分為兩種情況,要麽是當前節點的右節點,或者是本身 */ private Node deleteMin(Node node) { if(node.left == null) { return node.right; } node.left = deleteMin(root.left); return node; } public void deleteMax() { root = deleteMax(root); } /** * 刪除最大節點 * @param node * @return */ public Node deleteMax(Node node) { if(node.right == null) { return node.left; } node.right = deleteMax(node.right); return node; }
(2)刪除指定節點
public void delete(Key key) { root = delete(root,key); } public Node delete(Node node, Key key) { //1. 如果node為null,返回null 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 t = node; node = min(node.right); node.right = deleteMin(t.right); node.left = t.left; } //2. 否則返回當前節點 return node; }
9. 範圍查找
/** * 遍歷所有的key * @return */ public Iterable<Key> keys() { return keys(min(),max()); } /** * 遍歷指定範圍內的key * @param lo * @param hi * @return */ public Iterable<Key> keys(Key lo, Key hi) { Queue<Key> queue = new LinkedList<>(); keys(root,queue,lo,hi); return queue; } private void keys(Node node, Queue<Key> queue, Key lo, Key hi) { if(node == null) { return; } int cmplo = lo.compareTo(node.key); int cmphi = hi.compareTo(node.key); //如果最小鍵比當前鍵小,遍歷左樹 if(cmplo < 0) { keys(node.left,queue,lo,hi); } //如果最小鍵 <= 當前鍵 <= 最大鍵,添加到隊列 if(cmplo <= 0 && cmphi >= 0) { queue.add(node.key); } //如果最大鍵比當前鍵大,遍歷其右樹 if(cmplo > 0) { keys(node.left,queue,lo,hi); } }
數據結構------------------二叉查找樹(BST)的java實現