本文將主要以動圖方式展示二叉搜索樹的結構,以及動態操作;但是對於基本的概念和性質則不會有過多的提及,如果想系統了解建議查看鄧俊輝老師的《數據結構》課程;
一、結構概述
二叉樹:融合了向量的靜態操作(二分查找)和列表的動態操作(插入和刪除)的優點;使得樹成了應用廣泛的數據結構;
二叉搜索樹:即順序排列,可以搜索的樹就是二叉搜索樹;如下圖所示;

忽略二叉樹的大小,高度等信息的簡版結構如下:
public class BST<T extends Comparable<? super T>> {
private BSTNode<T> root;
public BST() {root = null;}
...
class BSTNode<T extends Comparable<? super T>> {
T key;
BSTNode<T> parent;
BSTNode<T> left;
BSTNode<T> right;
BSTNode(T key, BSTNode<T> parent, BSTNode<T> left, BSTNode<T> right) {
this.key = key;
this.parent = parent;
this.left = left;
this.right = right;
}
}
}
二、查找
根據二叉搜索樹左孩子節點小於父節點,右孩子節點大於等於父節點,逐步深入查找;

實現如下:
private BSTNode<T> search(BSTNode<T> x, T key) {
if (x == null) return x;
int cmp = key.compareTo(x.key);
if (cmp < 0)
return search(x.left, key);
else if (cmp > 0)
return search(x.right, key);
else
return x;
}
public BSTNode<T> search(T key) {
return search(root, key);
}
更好的實現還應該記錄查找失敗的上一個節點,除此之外還可以實現查找最大值,最小值;
三、插入
插入,同樣根據 BST 的排序規則,先查找插入位置,然后確定插入節點的引用關系;
1. 插入不重復節點

2. 插入重復節點

3. 實現
如果有 size 等信息,則需要從節點向上依次更新祖先節點
private void insert(T key) {
BSTNode<T> node = new BSTNode<T>(key, null, null, null);
BSTNode<T> r = this.root;
BSTNode<T> p = null;
int cmp;
// 查找插入位置
while (r != null) {
p = r;
cmp = node.key.compareTo(r.key);
if (cmp < 0)
r = r.left;
else
r = r.right;
}
if (p == null)
this.root = node;
else {
node.parent = p;
cmp = node.key.compareTo(p.key);
if (cmp < 0)
p.left = node;
else
p.right = node;
}
}
四、刪除
刪除,同樣首先需要查找該節點,然后刪除;
1. 刪除不完全節點
目標節點沒有左孩子或者右孩子時,直接刪除節點,然后令其后代代替;

2. 刪除完全節點
當目標節點同時擁有左孩子和右孩子時:
- 需要首先找到目標節點的直接后繼(自然順序或者中序遍歷順序的下一個節點),目標節點右孩子一直往左;當然這里同樣可以找目標節點的前驅節點替換;
- 然后交換目標節點和后繼節點的位置;
- 最后同上面的刪除一樣,直接刪除節點;

3. 實現
注意這里只是實現的一種,還可以是可前驅進行交換
public void delete(T key) {
BSTNode<T> node = search(root, key);
if (node != null) delete(node);
node = null;
}
private BSTNode<T> delete(BSTNode<T> node) {
// 將完全節點和直接后繼交換
if (node.left != null && node.right != null) {
BSTNode<T> succ = successor(node);
node.key = succ.key;
node = succ;
}
// 將孩子節點的父親接到祖父上
BSTNode<T> child = node.left != null ? node.left : node.right;
if (child != null) child.parent = node.parent;
// 目標節點沒有父節點則是根節點
if (node.parent == null) root = child;
// 目標節點是左孩子時
else if (node == node.parent.left) node.parent.left = child;
// 目標節點是右孩子時
else node.parent.right = child;
return node;
}
private BSTNode<T> successor(BSTNode<T> node) {
BSTNode<T> succ = node.right;
while (succ.left != null) succ = succ.left;
return succ;
}
// 這里只是右子樹中的直接后繼;整個中序遍歷中直接后繼還可能是其父節點或者祖先節點;
五、前序遍歷
對於一個數據結構而言,了解他最直接的方式就是遍歷操作,而對於二叉樹則有前、中、后、層次遍歷四種;
1. 描述
- 首先遍歷根節點
- 再一次遞歸遍歷左子樹和右子樹;
具體過程如下:

2. 實現
// 遞歸實現
private void travPre(BSTNode<T> tree) {
if (tree == null) return;
System.out.print(tree.key + " ");
travPre(tree.left);
travPre(tree.right);
}
// 迭代實現
public void travPre2(BSTNode<T> tree) {
Stack<BSTNode<T>> stack = new Stack<>();
while (true) {
visitAlongVine(tree, stack);
if (stack.isEmpty()) break;
tree = stack.pop();
}
}
private void visitAlongVine(BSTNode<T> tree, Stack<BSTNode<T>> stack) {
while (tree != null) {
System.out.print(tree.key + " ");
stack.push(tree.right);
tree = tree.left;
}
}
六、中序遍歷
1. 描述
- 對於中序遍歷,則首先遍歷左子樹
- 再遍歷根節點
- 最后遍歷右子樹
具體過程如下:

2. 實現
// 遞歸實現
private void travIn(BSTNode<T> tree) {
if (tree == null) return;
travIn(tree.left);
System.out.print(tree.key + " ");
travIn(tree.right);
}
// 迭代實現
public void travIn2(BSTNode<T> tree) {
Stack<BSTNode<T>> stack = new Stack<>();
while (true) {
goAlongVine(tree, stack);
if (stack.isEmpty()) break;
tree = stack.pop();
System.out.print(tree.key + " ");
tree = tree.right;
}
}
private void goAlongVine(BSTNode<T> tree, Stack<BSTNode<T>> stack) {
while (tree != null) {
stack.push(tree);
tree = tree.left;
}
}
七、后序遍歷
1. 描述
- 對於后序遍歷,則首先遍歷左子樹
- 再遍歷根右子樹
- 最后遍歷根節點
具體過程如下:

2. 實現
// 遞歸實現
private void travPost(BSTNode<T> tree) {
if (tree == null) return;
travPost(tree.left);
travPost(tree.right);
System.out.print(tree.key + " ");
}
// 迭代實現
public void travPost2(BSTNode<T> tree) {
Stack<BSTNode<T>> stack = new Stack<>();
if (tree != null) stack.push(tree);
while (!stack.isEmpty()) {
if (stack.peek() != tree.parent)
gotoHLVFL(stack);
tree = stack.pop();
System.out.print(tree.key + " ");
}
}
private void gotoHLVFL(Stack<BSTNode<T>> stack) {
BSTNode<T> tree;
while ((tree = stack.peek()) != null)
if (tree.left != null) {
if (tree.right != null) stack.push(tree.right);
stack.push(tree.left);
} else
stack.push(tree.right);
stack.pop();
}
八、層次遍歷
1. 描述
對於層次遍歷而言就相對簡單,即由上到下逐層遍歷;
2. 實現
public void travLevel() {
Queue<BSTNode<T>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
BSTNode<T> x = queue.poll();
System.out.print(x.key + " ");
if (x.left != null) queue.offer(x.left);
if (x.right != null) queue.offer(x.right);
}
}
總結
- 正是因為二叉樹同時具有向量和列表的特性,所以他的使用場景非常廣泛,當然他還有很多的變種,這些我們后續會繼續講到;
- 對於文中的動圖大家可以在這個網站自行實驗;http://btv.melezinek.cz/binary-search-tree.html