微信搜尋:碼農StayUp
主頁地址:https://gozhuyinglong.github.io
原始碼分享:https://github.com/gozhuyinglong/blog-demos
1. 二叉查詢樹(Binary Search Tree)
二叉查詢樹又叫二叉排序樹(Binary Sort Tree),或叫二叉搜尋樹,簡稱BST。BST是一種節點值之間有次序的二叉樹。其特性是:
- 若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
- 若任意節點的右子樹不空,則右子樹上所有節點的值均大於或等於它的根節點的值;
- 任意節點的左、右子樹也分別為二叉查詢樹;
二叉查詢樹相比於其他資料結構的優勢在於查詢、插入的時間複雜度較低,為$O(logN)$。用大$O$符號表示的時間複雜度:
演算法 | 平均 | 最差 |
---|---|---|
空間 | $O(N)$ | $O(N)$ |
搜尋 | $O(logN)$ | $O(N)$ |
插入 | $O(logN)$ | $O(N)$ |
刪除 | $O(logN)$ | $O(N)$ |
2. BST的實現
二叉查詢樹要求所有的節點元素都能夠排序,所以我們的Node
節點類需要實現Comparable
介面,樹中的兩個元素可以使用compareTo
方法進行比較。
我們節點中元素的型別為int
型,所以該介面泛型為Comparable<Integer>
,下面是具體實現:
2.1 節點類
- element 為資料元素
- left 為左子節點
- right 為右子節點
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);
}
}
2.2 二叉查詢樹類
- root 為樹根,所有的操作均始於此
後面會在該類中增加其他方法,如新增、查詢、刪除等
class BinarySearchTree {
private Node root; // 樹根
}
3. 插入節點
向二叉查詢樹中插入的節點總是葉子節點,插入過程如下:
- 若
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;
}
}
4. 查詢節點
通過二叉查詢樹查詢元素,其過程如下:
- 若
root
為空,則查詢失敗 - 將當前元素與目標元素對比,若相等則查詢成功。
- 若不相等,則繼續遞迴查詢:若目標值小於當前節點值,則查詢左子樹;否則,查詢右子樹。
具體實現:
在BinarySearchTree
類中新增兩個方法:
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;
}
}
5. 遍歷節點
BST是一個有序二叉樹,通過中序遍歷可順序輸出樹中節點。
中序遍歷過程如下:
- 遞迴遍歷左子節點
- 輸出當前節點
- 遞迴遍歷右子節點
具體實現:
在BinarySearchTree
類中新增兩個方法:
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);
}
}
6. 刪除節點
刪除節點最為複查,共有三種情況:
6.1 目標元素為葉子節點
葉子節點最容易刪除,過程如下:
- 找到目標節點的父節點
- 判斷目標節點是父節點的左子樹還是右子樹
- 若是左子樹,將父節點的
left
設為空;否則將父節點的right
設為空
6.2 目標元素即有左子樹,也有右子樹
該情況刪除操作最為複雜,過程如下:
- 找到目標節點的父節點
- 判斷目標節點是父節點的左子樹還是右子樹
- 找到右子樹中最小元素(葉子節點),將其賦給臨時變數
minNode
,再將該元素從樹中刪除 - 將目標元素的屬性賦予
minNode
。 - 若目標元素是父節點的左子樹,將父節點的
left
設為minNode
;否則將父節點的right
設為minNode
6.3 目標元素只有左子樹,或只有右子樹
刪除過程如下
- 找到目標節點的父節點
- 判斷目標節點是父節點的左子樹還是右子樹
- 若是左子樹,將父節點的
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;
}
}
7. 完整程式碼
該程式碼根據下圖二叉查詢樹實現,其操作包括:新增、查詢、遍歷、刪除、查詢最小值、查詢最大值。
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
推薦閱讀
- 《 陣列 》
- 《 稀疏陣列 》
- 《 連結串列(單連結串列、雙連結串列、環形連結串列) 》
- 《 棧 》
- 《 佇列 》
- 《 樹 》
- 《 二叉樹 》
- 《 二叉查詢樹(BST) 》
- 《 AVL樹(平衡二叉樹) 》
- 《 B樹 》
- 《 雜湊表(雜湊表) 》