二叉樹
跟鏈表一樣诽凌,二叉樹也是一種動態(tài)數(shù)據(jù)結(jié)構(gòu)毡熏,即,不需要在創(chuàng)建時指定大小侣诵。
跟鏈表不同的是痢法,二叉樹中的每個節(jié)點,除了要存放元素e杜顺,它還有兩個指向其它節(jié)點的引用财搁,分別用Node left和Node right來表示。
類似的躬络,如果每個節(jié)點中有3個指向其它節(jié)點的引用尖奔,就稱其為"三叉樹"...
二叉樹具有唯一的根節(jié)點。
二叉樹中每個節(jié)點最多指向其它的兩個節(jié)點穷当,我們稱這兩個節(jié)點為"左孩子"和"右孩子"提茁,即每個節(jié)點最多有兩個孩子。
一個孩子都沒有的節(jié)點馁菜,稱之為"葉子節(jié)點"茴扁。
二叉樹的每個節(jié)點,最多只能有一個父親節(jié)點汪疮,沒有父親節(jié)點的節(jié)點就是"根節(jié)點"峭火。
二叉樹的形象化描述如下圖:
二叉樹具有天然的遞歸結(jié)構(gòu)毁习。
1.每個節(jié)點的"左子樹"也是一棵二叉樹,每個節(jié)點的"右子樹"也是一棵二叉樹躲胳。
- 二叉樹不一定是"滿的"蜓洪,即,某些節(jié)點可能只有一個子節(jié)點坯苹;更極端一點,整棵二叉樹可以僅有一個節(jié)點摇天;在極端一點粹湃,整棵二叉樹可以一個節(jié)點都沒有;
實現(xiàn)二分搜索樹
二分搜索樹的構(gòu)造函數(shù)泉坐、getSize方法为鳄、isEmpty方法及add方法的實現(xiàn)邏輯如下:
public class BST<E extends Comparable> {
private class Node {
public E e;
public Node left, right;
// 構(gòu)造函數(shù)
public Node(E e) {
this.e = e;
left = null;
right = null;
}
}
private Node root;
private int size; // 記錄二分搜索樹中存儲的元素個數(shù)
public BST() {
root = null;
size = 0;
}
// 實現(xiàn)size方法
public int size() {
return size;
}
// 實現(xiàn)isEmpty方法
public boolean isEmpty() {
return size == 0;
}
// 實現(xiàn)add方法
public void add(E e) {
root = add(root, e);
}
// 向以node為根的二分搜索樹中插入元素e,遞歸算法
// 返回插入新節(jié)點后二分搜索樹的根
private Node add(Node node, E e) {
if (node == null) {
size++;
return new Node(e);
}
if (e.compareTo(node.e) < 0) {
node.left = add(node.left, e);
} else if (e.compareTo(node.e) > 0) {
node.right = add(node.right, e);
}
return node;
}
}
二分搜索樹的contains方法實現(xiàn)邏輯如下:
// 實現(xiàn)contains方法腕让,判斷二分搜索樹中是否包含元素e
public boolean contains(E e) {
return contains(root, e);
}
// 判斷以node為根的二分搜索樹中是否包含元素e
private boolean contains(Node node, E e) {
if (node == null) {
return false;
}
if (e.compareTo(node.e) == 0) {
return true;
} else if (e.compareTo(node.e) < 0) {
return contains(node.left, e);
} else {
return contains(node.right, e);
}
}
二分搜索樹的遍歷操作孤钦,遍歷操作就是把所有節(jié)點都訪問一遍
前序遍歷的業(yè)務(wù)邏輯如下:
//二分搜索樹的前序遍歷
public void preOder() {
preOrder(root);
}
private void preOrder(Node node) {
if (node == null) {
return;
}
System.out.print(node.e);
preOrder(node.left);
preOrder(node.right);
}
中序遍歷的業(yè)務(wù)邏輯如下:
// 二分搜索樹的中序遍歷
public void inOrder() {
inOrder(root);
}
// 中序遍歷以node為根的二分搜索樹,遞歸算法
private void inOrder(Node node) {
if (node == null) {
return;
}
inOrder(node.left);
System.out.print(node.e);
inOrder(node.right);
}
后序遍歷的業(yè)務(wù)邏輯如下:
// 二分搜索樹的后序遍歷
public void postOrder() {
postOrder(root);
}
// 后序遍歷以node為根的二分搜索樹纯丸,遞歸算法
private void postOrder(Node node) {
if (node == null) {
return;
}
postOrder(node.left);
postOrder(node.right);
System.out.print(node.e);
}
簡單測試如下:
public class Main {
public static void main(String[] args) {
BST<Integer> bst = new BST<>();
int[] nums = {5, 3, 6, 8, 4, 2};
for (int num : nums) {
// 測試add方法
bst.add(num);
}
// 測試前序遍歷
bst.preOrder();
System.out.println();
// 測試中序遍歷
bst.inOrder();
System.out.println();
// 測試后序遍歷
bst.postOrder();
}
}
輸出結(jié)果:
532468
234568
243865
前序遍歷是最自然的遍歷方式偏形,也是最常用的遍歷方式;中序遍歷的結(jié)果是按從小到大的順序的排列的觉鼻;后序遍歷可以用于為二分搜索樹釋放內(nèi)存俊扭。
利用"棧"實現(xiàn)二分搜索樹的非遞歸前序遍歷
// 二分搜索樹的非遞歸前序遍歷
public void preOrderNR() {
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node cur = stack.pop();
System.out.print(cur.e);
if (cur.right != null) {
stack.push(cur.right);
}
if (cur.left != null) {
stack.push(cur.left);
}
}
}
二分搜索樹的非遞歸實現(xiàn)比遞歸實現(xiàn)更加復(fù)雜。
二分搜索樹的前序坠陈、中序和后續(xù)遍歷都屬于"深度優(yōu)先"算法萨惑。
二分搜索樹的"層序遍歷"屬于"廣度優(yōu)先"算法。
利用"隊列"實現(xiàn)二分搜索樹的"層序遍歷"
// 二分搜索樹的層序遍歷
public void levelOrder() {
Queue<Node> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
Node cur = q.remove();
System.out.print(cur.e);
if (cur.left != null) {
q.add(cur.left);
}
if (cur.right != null) {
q.add(cur.right);
}
}
}
獲取二分搜索樹中的最小元素和最大元素
// 尋找二分搜索樹中的最小元素
public E minimum() {
if (size == 0) {
throw new IllegalArgumentException("BST is empty.");
}
return minimum(root).e;
}
// 返回以node為根的二分搜索樹的最小元素所在節(jié)點
private Node minimum(Node node) {
if (node.left == null) {
return node;
}
return minimum(node.left);
}
// 尋找二分搜索樹中的最大元素
public E maximum() {
if (size == 0) {
throw new IllegalArgumentException("BST is empty.");
}
return maximum(root).e;
}
// 返回以node為根的二分搜索樹的最大元素所在節(jié)點
private Node maximum(Node node) {
if (node.right == null) {
return node;
}
return maximum(node.right);
}
刪除二分搜索樹中最小元素和最大元素所在節(jié)點
// 從二分搜索樹中刪除最小元素所在節(jié)點仇矾,返回最小元素
public E removeMin() {
E ret = minimum();
root = removeMin(root);
return ret;
}
// 刪除掉以node為根的二分搜索樹中的最小元素所在節(jié)點
// 返回刪除節(jié)點后新的二分搜索樹的根
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
// 從二分搜索樹中刪除最大元素所在節(jié)點庸蔼,返回最小元素
public E removeMax() {
E ret = maximum();
root = removeMax(root);
return ret;
}
// 刪除掉以node為根的二分搜索樹中的最小元素所在節(jié)點
// 返回刪除節(jié)點后新的二分搜索樹的根
private Node removeMax(Node node) {
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
node.right = removeMax(node.right);
return node;
}
刪除二分搜索樹中指定元素所對應(yīng)的節(jié)點
// 從二分搜索樹中刪除元素為e的節(jié)點
public void remove(E e) {
remove(root, e);
}
// 刪除以node為根節(jié)點的二分搜索樹中元素為e的節(jié)點,遞歸算法
// 返回刪除節(jié)點后新的二分搜索樹的根
private Node remove(Node node, E e) {
if (node == null) {
return null;
}
if (e.compareTo(node.e) < 0) {
node.left = remove(node.left, e);
return node;
} else if (e.compareTo(node.e) > 0) {
node.right = remove(node.right, e);
return node;
} else {
// 待刪除節(jié)點左子樹為空的情況
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
// 待刪除節(jié)點右子樹為空的情況
} else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
// 待刪除節(jié)點左右子樹均不為空
// 找到比待刪除節(jié)點大的最小節(jié)點贮匕,即待刪除節(jié)點右子樹的最小節(jié)點
// 用這個節(jié)點頂替待刪除節(jié)點
} else {
Node successor = minimum(node.right);
successor.right = removeMin(node.right); //這里進行了size--操作
successor.left = node.left;
node.left = null;
node.right = null;
return successor;
}
}
}
```