二叉查詢樹(Binary Search Tree,簡稱BST)是一棵二叉樹,它的左子節點的值比父節點的值要小,右節點的值要比父節點的值大。它的高度決定了它的查詢效率。
在理想的情況下,二叉查詢樹增刪查改的時間複雜度為O(logN)(其中N為節點數),最壞的情況下為O(N)。當它的高度為logN+1時,我們就說二叉查詢樹是平衡的。
首先定義二叉樹節點
class TreeNode {
TreeNode(int value) {
this.value = value;
}
TreeNode left;
TreeNode right;
int value;
}
複製程式碼
本文中rootNode
為建立二叉搜尋樹的root節點,在類初始化階段賦值。
private final TreeNode rootNode;
public BinarySearchTree(int value) {
rootNode = new TreeNode(value);
}
複製程式碼
插入操作:
首先從root節點開始迴圈遍歷,
- 如果插入節點等於當前root節點,直接結束不做任何處理。
- 如果插入節點小於當前root節點,從左節點開始查詢。 (root=root.left)
- 如果插入節點大於當前root節點,從右節點開始查詢。 (root=root.right)
- 噹噹前節點的左節點或者右節點為空時,插入新節點,結束過程。
上程式碼:
public TreeNode insert(int value) {
TreeNode currentRoot = rootNode;
TreeNode newNode = new TreeNode(value);
while (true) {
if (value == currentRoot.value) {
return null;
}
else if (value < currentRoot.value) {
if (currentRoot.left != null) {
currentRoot = currentRoot.left;
}
else {
return currentRoot.left = newNode;
}
}
else {
if (currentRoot.right != null) {
currentRoot = currentRoot.right;
}
else {
return currentRoot.right = newNode;
}
}
}
}
複製程式碼
查詢操作:
類似於插入操作:
- 如果插入節點等於root節點,查詢完成。
- 如果插入節點小於root節點,從left節點開始查詢。 (root=root.left)
- 如果插入節點大於root節點,從right節點開始查詢。(root=root.right)
上程式碼:
public TreeNode get(int value) {
TreeNode currentRoot = rootNode;
while (true) {
if (currentRoot == null) {
return null;
}
if (value == currentRoot.value) {
return currentRoot;
}
else if (value < currentRoot.value) {
currentRoot = currentRoot.left;
}
else {
currentRoot = currentRoot.right;
}
}
}
複製程式碼
遍歷操作:
遍歷分為
- 前序:根節點放在左節點的左邊 (根→左→右)
- 中序:根節點放在左節點和右節點的中間(左→根→右)
- 後序:根節點放在右節點的右邊 (左→右→根)
中序遍歷的程式碼:
public void getTreeByInOrder(List<TreeNode> list, TreeNode root) {
if (root != null) {
getTreeByInOrder(list, root.left);
list.add(root);
getTreeByInOrder(list, root.right);
}
}
複製程式碼
前序後序和其類似,就不貼程式碼了
層次遍歷(也可以叫做廣度優先遍歷):
思路:
-
由根從上往下迴圈遍歷,利用佇列(queue)先進先出的原則,每訪問一個節點之後,將其左右子節點入隊。這樣,同一層的所有節點肯定先於下一層的節點先訪問
-
如果需要分層儲存節點呢?(比如需要換行列印):問題的難點是如何知道本層已經遍歷完了,可以使用兩個指標last和nextLast,一個指向當前層的最後一個節點,一個指向下一層的最後一個節點,這樣難點轉換成了last和nextLast的更新。當節點入隊時,更新nextLast(設定nextLast為佇列的最後一個節點),因為佇列中的最後一個節點一定是下一層的最後節點。
上程式碼:
public List<List<Integer>> printTreeByHierarchy() {
TreeNode currentRoot = rootNode;
Queue<TreeNode> queue = new LinkedList<>();
TreeNode last = currentRoot;
TreeNode nextLast = null;
queue.offer(currentRoot);
List<List<Integer>> lists = new ArrayList<>();
List<Integer> nodes = new ArrayList<>();
while (!queue.isEmpty()) {
currentRoot = queue.poll();
nodes.add(currentRoot.value);
if (currentRoot.left != null) {
queue.offer(currentRoot.left);
nextLast = currentRoot.left;
}
if (currentRoot.right != null) {
queue.offer(currentRoot.right);
nextLast = currentRoot.right;
}
if (last == currentRoot) {//因為last是當前層的最後一個節點,如果等式成立,說明這層已經遍歷完
last = nextLast;
lists.add(nodes);
nodes = new ArrayList<>();
}
}
System.out.println(lists);
return lists;
}
複製程式碼
刪除操作:
刪除操作比較麻煩,需要分4種情況考慮。我們以上面這顆樹作為參考:
- 如果刪除節點含有左右子節點:需要用刪除節點的後繼節點(以中序遍歷)取代刪除節點的位置。----比如節點刪除20,需要用26代替20的位置
- 如果刪除節點只含有單個節點(或左或右):需要用刪除節點的左(右)節點取代刪除節點的位置。----比如節點刪除27,需要用26代替27的位置
- 如果刪除節點無子節點(是葉子節點):直接刪除。
上程式碼:
public void remove(int value) {
TreeNode currentRoot = rootNode;
while (true) {
if (currentRoot == null) {
return;//沒找到,返回或者拋異常
}
if (value < currentRoot.value) {
currentRoot = currentRoot.left;
}
else if (value > currentRoot.value) {
currentRoot = currentRoot.right;
}
else {
TreeNode replacement;//代替者
if (currentRoot.left == null && currentRoot.right == null) {//無雙子節點
resetParentNode(currentRoot, null);
}
else if (currentRoot.left != null && currentRoot.right != null) {//雙子節點
replacement = getSuccessor(currentRoot);
replacement.left = currentRoot.left;
replacement.right = currentRoot.right;
resetParentNode(replacement, null); //重設後繼節點的父節點
resetParentNode(currentRoot, replacement);//重設正在移除節點的父節點
}
else if (currentRoot.left != null) {
resetParentNode(currentRoot, currentRoot.left); //
} //
else { //單節點
resetParentNode(currentRoot, currentRoot.right); //
} //
return;
}
}
}
複製程式碼
所有程式碼:BinarySearchTree.java
如果文章中有分析錯誤的地方或者值得改進的地方,歡迎大家指出。
參考文章: