項目主頁: https://github.com/gozhuyinglong/blog-demos
本文源碼: https://github.com/gozhuyinglong/blog-demos/tree/main/java-data-structures
二叉查找樹又叫二叉排序樹(Binary Sort Tree),或叫二叉搜索樹,簡稱BST。BST是一種節點值之間有次序的二叉樹。其特性是:java
二叉查找樹相比於其餘數據結構的優點在於查找、插入的時間複雜度較低,爲$O(logN)$。用大$O$符號表示的時間複雜度:node
算法 | 平均 | 最差 |
---|---|---|
空間 | $O(N)$ | $O(N)$ |
搜索 | $O(logN)$ | $O(N)$ |
插入 | $O(logN)$ | $O(N)$ |
刪除 | $O(logN)$ | $O(N)$ |
二叉查找樹要求全部的節點元素都可以排序,因此咱們的Node
節點類須要實現Comparable
接口,樹中的兩個元素可使用compareTo
方法進行比較。
咱們節點中元素的類型爲int
型,因此該接口泛型爲Comparable<Integer>
,下面是具體實現:git
class Node implements Comparable<Integer> { private final int element; // 數據元素 private Node left; // 左子樹 private Node right; // 右子樹 private Node(Integer element) { this.element = element; } @Override public int compareTo(Integer o) { return o.compareTo(element); } }
後面會在該類中增長其餘方法,如添加、查找、刪除等github
class BinarySearchTree { private Node root; // 樹根 }
向二叉查找樹中插入的節點老是葉子節點,插入過程以下:算法
root
爲空,則將插入節點設爲root
compareTo
進行比較,若插入元素值小,而且左子節點left
爲空,則插入至當前節點左子節點;不然繼續遞歸right
爲空,則插入至當前節點右子節點;不然繼續遞歸。具體實現:
在BinarySearchTree
類中添加兩個方法:數據結構
public boolean add(int element)
爲公開方法private boolean add(Node node, int element)
爲私有方法,內部遞歸使用// 添加元素 public boolean add(int element) { if (root == null) { root = new Node(element); return true; } return add(root, element); } // 添加元素(遞歸) private boolean add(Node node, int element) { if (node.compareTo(element) < 0) { if (node.left == null) { node.left = new Node(element); return true; } else { return add(node.left, element); } } else if (node.compareTo(element) > 0) { if (node.right == null) { node.right = new Node(element); return true; } else { return add(node.right, element); } } else { return false; } }
經過二叉查找樹查找元素,其過程以下:ide
root
爲空,則查找失敗具體實現:
在BinarySearchTree
類中添加兩個方法:this
public Node find(int element)
爲公開方法private Node find(Node node, int element)
爲私有方法,內部遞歸使用// 查找元素 public Node find(int element) { if (root == null) { return null; } return find(root, element); } // 查詢元素(遞歸) private Node find(Node node, int element) { if (node == null) { return null; } int compareResult = node.compareTo(element); if (compareResult < 0) { return find(node.left, element); } else if (compareResult > 0) { return find(node.right, element); } else { return node; } }
BST是一個有序二叉樹,經過中序遍歷可順序輸出樹中節點。
中序遍歷過程以下:spa
具體實現:
在BinarySearchTree
類中添加兩個方法:code
public void orderPrint()
爲公開方法private void orderPrint(Node node)
爲私有方法,內部遞歸使用// 遍歷節點 public void orderPrint() { orderPrint(root); } // 遍歷節點(遞歸) private void orderPrint(Node node) { if (node == null) { return; } // 遞歸左子節點 if (node.left != null) { orderPrint(node.left); } // 輸出當前節點 System.out.println(node.element); // 遞歸右子節點 if (node.right != null) { orderPrint(node.right); } }
刪除節點最爲複查,共有三種狀況:
葉子節點最容易刪除,過程以下:
left
設爲空;不然將父節點的right
設爲空該狀況刪除操做最爲複雜,過程以下:
minNode
,再將該元素從樹中刪除minNode
。left
設爲minNode
;不然將父節點的right
設爲minNode
刪除過程以下
left
設爲目標節點不爲空的子樹;不然將父節點的right
設爲目標節點不爲空的子樹具體實現
在BinarySearchTree
類中添加兩個方法:
public boolean remove(int element)
爲公開方法private boolean remove(Node parentNode, Node node, int element)
爲私有方法,內部遞歸使用// 刪除節點 public boolean remove(int element) { if (root == null) { return false; } // 若是刪除的元素是root if (root.compareTo(element) == 0) { if (root.right == null) { root = root.left; } else { root.right.left = root.left; root = root.right; } return true; } return remove(null, root, element); } // 刪除節點(遞歸) private boolean remove(Node parentNode, Node node, int element) { if (node == null) { return false; } // 先找到目標元素 int compareResult = node.compareTo(element); if (compareResult < 0) { return remove(node, node.left, element); } if (compareResult > 0) { return remove(node, node.right, element); } // 找到目標元素,判斷該節點是父節點的左子樹仍是右子樹 boolean isLeftOfParent = false; if (parentNode.left != null && parentNode.left.compareTo(element) == 0) { isLeftOfParent = true; } // 刪除目標元素 if (node.left == null && node.right == null) { // (1)目標元素爲葉子節點,直接刪除 if (isLeftOfParent) { parentNode.left = null; } else { parentNode.right = null; } } else if (node.left != null && node.right != null) { // (2)目標元素即有左子樹,也有右子樹 // 找到右子樹最小值(葉子節點),並將其刪除 Node minNode = findMin(node.right); remove(minNode.element); // 將該最小值替換要刪除的目標節點 minNode.left = node.left; minNode.right = node.right; if(isLeftOfParent) { parentNode.left = minNode; } else { parentNode.right = minNode; } } else { // (3)目標元素只有左子樹,或只有右子樹 if (isLeftOfParent) { parentNode.left = node.left != null ? node.left : node.right; } else { parentNode.right = node.left != null ? node.left : node.right; } } return true; } }
該代碼根據下圖二叉查找樹實現,其操做包括:添加、查找、遍歷、刪除、查詢最小值、查詢最大值。
public class BinarySearchTreeDemo { public static void main(String[] args) { BinarySearchTree tree = new BinarySearchTree(); System.out.println("----------------------添加元素"); Integer[] array = {5, 2, 7, 1, 4, 3, 7, 6, 9, 8}; for (Integer element : array) { System.out.printf("添加元素[%s] --> %s\n", element, tree.add(element)); } System.out.println("----------------------順序輸出(中序遍歷)"); tree.orderPrint(); System.out.println("----------------------查找元素"); System.out.println(tree.find(7)); System.out.println("----------------------查找最小元素"); System.out.println(tree.findMin()); System.out.println("----------------------查找最大元素"); System.out.println(tree.findMax()); System.out.println("----------------------是否包含元素"); System.out.println("是否包含[0] --> \t" + tree.contains(0)); System.out.println("是否包含[2] --> \t" + tree.contains(2)); System.out.println("----------------------刪除目標元素"); System.out.println("刪除[0] --> \t" + tree.remove(0)); tree.orderPrint(); System.out.println("刪除[1] --> \t" + tree.remove(1)); tree.orderPrint(); System.out.println("刪除[4] --> \t" + tree.remove(4)); tree.orderPrint(); System.out.println("刪除[7] --> \t" + tree.remove(7)); tree.orderPrint(); } private static class BinarySearchTree { private Node root; // 樹根 /** * 添加元素 * * @param element * @return */ public boolean add(int element) { if (root == null) { root = new Node(element); return true; } return add(root, element); } /** * 添加元素(遞歸) * * @param node * @param element * @return */ private boolean add(Node node, int element) { if (node.compareTo(element) < 0) { if (node.left == null) { node.left = new Node(element); return true; } else { return add(node.left, element); } } else if (node.compareTo(element) > 0) { if (node.right == null) { node.right = new Node(element); return true; } else { return add(node.right, element); } } else { return false; } } /** * 查詢元素 * * @param element * @return */ public Node find(int element) { if (root == null) { return null; } return find(root, element); } /** * 查詢元素(遞歸) * * @param node * @param element * @return */ private Node find(Node node, int element) { if (node == null) { return null; } int compareResult = node.compareTo(element); if (compareResult < 0) { return find(node.left, element); } else if (compareResult > 0) { return find(node.right, element); } else { return node; } } /** * 查找最大值 * * @return */ public Node findMax() { return findMax(root); } /** * 查找最大值(遞歸) * * @param node * @return */ private Node findMax(Node node) { if (node.right == null) { return node; } return findMax(node.right); } /** * 查找最小值 * * @return */ private Node findMin() { return findMin(root); } /** * 查找最小值(遞歸) * * @param node * @return */ private Node findMin(Node node) { if (node.left == null) { return node; } return findMin(node.left); } /** * 順序輸出 */ public void orderPrint() { orderPrint(root); } /** * 順序輸出(遞歸) * * @param node */ private void orderPrint(Node node) { if (node == null) { return; } // 遞歸左子節點 if (node.left != null) { orderPrint(node.left); } // 輸出當前節點 System.out.println(node.element); // 遞歸右子節點 if (node.right != null) { orderPrint(node.right); } } /** * 是否包含某值 * * @param element * @return */ public boolean contains(int element) { if (find(element) == null) { return false; } return true; } /** * 刪除目標元素 * * @param element * @return */ public boolean remove(int element) { if (root == null) { return false; } // 若是刪除的元素是root if (root.compareTo(element) == 0) { if (root.right == null) { root = root.left; } else { root.right.left = root.left; root = root.right; } return true; } return remove(null, root, element); } /** * 刪除目標元素(遞歸),有三種狀況: * (1)目標元素爲葉子節點 * (2)目標元素即有左子樹,也有右子樹 * (3)目標元素只有左子樹,或只有右子樹 * * @param parentNode 當前節點的父節點 * @param node 當前節點(若當前節點上的元素與要刪除的元素匹配,則刪除當前節點) * @param element 要刪除的元素 * @return */ private boolean remove(Node parentNode, Node node, int element) { if (node == null) { return false; } // 先找到目標元素 int compareResult = node.compareTo(element); if (compareResult < 0) { return remove(node, node.left, element); } if (compareResult > 0) { return remove(node, node.right, element); } // 找到目標元素,判斷該節點是父節點的左子樹仍是右子樹 boolean isLeftOfParent = false; if (parentNode.left != null && parentNode.left.compareTo(element) == 0) { isLeftOfParent = true; } // 刪除目標元素 if (node.left == null && node.right == null) { // (1)目標元素爲葉子節點,直接刪除 if (isLeftOfParent) { parentNode.left = null; } else { parentNode.right = null; } } else if (node.left != null && node.right != null) { // (2)目標元素即有左子樹,也有右子樹 // 找到右子樹最小值(葉子節點),並將其刪除 Node minNode = findMin(node.right); remove(minNode.element); // 將該最小值替換要刪除的目標節點 minNode.left = node.left; minNode.right = node.right; if(isLeftOfParent) { parentNode.left = minNode; } else { parentNode.right = minNode; } } else { // (3)目標元素只有左子樹,或只有右子樹 if (isLeftOfParent) { parentNode.left = node.left != null ? node.left : node.right; } else { parentNode.right = node.left != null ? node.left : node.right; } } return true; } } private static class Node implements Comparable<Integer> { private final Integer element; // 數據元素 private Node left; // 左子樹 private Node right; // 右子樹 private Node(Integer element) { this.element = element; } @Override public int compareTo(Integer o) { return o.compareTo(element); } @Override public String toString() { return "Node{" + "element=" + element + '}'; } } }
輸出結果:
----------------------添加元素 添加元素[5] --> true 添加元素[2] --> true 添加元素[7] --> true 添加元素[1] --> true 添加元素[4] --> true 添加元素[3] --> true 添加元素[7] --> false 添加元素[6] --> true 添加元素[9] --> true 添加元素[8] --> true ----------------------順序輸出(中序遍歷) 1 2 3 4 5 6 7 8 9 ----------------------查找元素 Node{element=7} ----------------------查找最小元素 Node{element=1} ----------------------查找最大元素 Node{element=9} ----------------------是否包含元素 是否包含[0] --> false 是否包含[2] --> true ----------------------刪除目標元素 刪除[0] --> false 1 2 3 4 5 6 7 8 9 刪除[1] --> true 2 3 4 5 6 7 8 9 刪除[4] --> true 2 3 5 6 7 8 9 刪除[7] --> true 2 3 5 6 8 9