二叉搜索樹
1 定義
二叉搜索樹,也稱爲二叉查找樹或者二叉排序樹。假設節點node是二叉搜索樹中的某個結點,結點node包含左右指針和數據值x,如果node結點的左結點上存在left結點(數據值爲y),則y值小於x值,如果node結點的右結點上存在right結點(數據值爲z),則z值大於x值。因爲這個特性,它經常用於二分查找。
2 性質
- 若樹中任意結點的左子樹不爲空,則左子樹上的所有結點的值均小於該結點的值;
- 若樹中任意結點的右子樹不爲空,則右子樹上的所有結點的值均大於該結點的值;
- 樹中任意結點的左、右子樹都爲二叉搜索樹;
3 結點結構
二叉搜索樹的結點結構跟普通二叉樹結點一樣,擁有左孩子的指針,右孩子的指針以及數據域
。可以用下面的圖來表示:
Java代碼定義爲:
class TreeNode<T> {
TreeNode<T> leftNode;
TreeNode<T> rightNode;
T nodeValue;
public TreeNode(T object) {
this.nodeValue = object;
}
}
4 創建二叉搜索樹
首先給出一個無序的數組int[] num = {11,10,9,22,13,30,17},我們以這個無序數組爲例建立一個二叉搜索樹:
第一步:i = 0,num[0]=11,二叉搜索樹爲空樹,則根節點爲11;
第二步:i = 1,num[1]=10,與根結點11比較,小於根結點且根結點的左子樹爲空,則作爲根結點的左結點;
第三步:i = 2, num[2]=9,小於根結點11且左結點不爲空,繼續與根結點的左結點10比較,小於10且該結點的左結點爲空,則作爲10的左結點;
第四步:i = 3,num[3]=22,大於根節點11且根結點的右結點爲空,則作爲根結點的右結點;
第五步:i = 4,num[4]=13,大於根結點11且根結點的右結點不爲空,小於根結點的右結點,且其左結點爲空,則作爲根結點的右結點的左結點;
第六步:i = 5,num[5]= 30,大於根節點11且根結點的右結點不爲空,大於根結點的右結點,且其右結點爲空,則作爲根結點的右結點的右結點;
第七步:i = 6,num[6]=17,大於根結點11且根結點的右結點不爲空,小於根結點的右結點,且其左結點不爲空,大於其左結點且其左結點的右結點爲空,則作爲根結點的右結點的左結點的右結點;
則其最後的二叉樹爲:
注意:二叉搜索樹的中序遍歷就是一個排序的數組。
用Java代碼實現整個插入的過程:
/**
* 二叉搜索樹插入
* @param value
*/
public void insert(Integer value) {
TreeNode<Integer> node = new TreeNode<Integer>(value);
if (rootNode == null) {
rootNode = node; // 賦值根結點
return;
}
TreeNode<Integer> currentNode = rootNode;
TreeNode<Integer> parentNode = rootNode;
boolean isLeftNode = false;
while (currentNode != null) {
parentNode = currentNode;
if ((int)value > (int)parentNode.nodeValue) {
currentNode = currentNode.rightNode; // 大於當前節點的值則往右子樹查找
isLeftNode = false;
} else if ((int)value < (int)parentNode.nodeValue) {
currentNode = currentNode.leftNode; // 小於當前節點的值則往左子樹查找
isLeftNode = true;
} else {
return; // 如果與結點值相同則結束
}
}
if (isLeftNode) {
parentNode.leftNode = node;
} else {
parentNode.rightNode = node;
}
}
插入完成後則可以用利用中序遍歷打印二叉樹的節點,看看是否是排好序的數組。我們這裏給出二叉樹的前序、中序和後序遍歷
的Java代碼:
/**
* 中序遍歷
* @param root 根結點
*/
public void middleOrder(TreeNode<T> root) {
if (root != null) {
middleOrder(root.leftNode);
System.out.println(root.nodeValue + " ");
middleOrder(root.rightNode);
}
}
/**
* 前序遍歷
* @param root 根結點
*/
public void preOrder(TreeNode<T> root) {
if (root != null) {
System.out.println(root.nodeValue + " ");
middleOrder(root.leftNode);
middleOrder(root.rightNode);
}
}
/**
* 後序遍歷
* @param root 根結點
*/
public void postOrder(TreeNode<T> root) {
if (root != null) {
middleOrder(root.leftNode);
middleOrder(root.rightNode);
System.out.println(root.nodeValue + " ");
}
}
5 查找過程
二叉搜索樹的查找過程:
- 若樹爲空,則查找結束,無匹配;
- 如果當前結點的值與需要查找的值相等,則查找成功;
- 如果當前結點的值大於查找的值,則遞歸查找結點的左結點;
- 如果當前結點的值小於查找的值,則遞歸查找結點的右結點;
Java代碼實現查找過程:
/**
* 查找結點
* @param value
* @return
*/
public boolean search(Integer value) {
if (rootNode == null) {
return false;
}
TreeNode<Integer> currentNode = rootNode;
TreeNode<Integer> parentNode = rootNode;
while (currentNode != null) {
parentNode = currentNode;
if ((int)value < (int)parentNode.nodeValue) {
currentNode = parentNode.leftNode;
} else if ((int)value > (int)parentNode.nodeValue) {
currentNode = parentNode.rightNode;
} else {
return true;
}
}
return false;
}
6 插入過程
二叉搜索樹的插入需要做兩件事:
- 一是與結點的值作比較,若有相等則插入結束;
- 二是把待插入結點插入到合適的位置;
我們以上面的二叉搜索樹爲例,插入值爲12的結點。插入過程和結果如圖所示:
插入過程的Java代碼同上面構建過程的insert方法.
7 刪除過程
二叉搜索樹的刪除稍微複雜一點,主要是因爲刪除結點後需要保證二叉搜索樹的性質。那麼主要分下面幾種情況討論:
- 刪除結點爲葉子節點,則根據其在父結點的左右,把父結點對應的子結點置爲null;
- 刪除結點只有左結點(左子樹),或者只有右結點(右子樹),則把左結點或者右結點直接替換當前結點;
- 刪除結點既有左子樹又有右子樹,情況就比較複雜,需要找到它的前驅結點或者後繼結點。前驅結點是小於它的最大結點,後繼結點是大於他的最小結點;前面講過二叉搜索樹的中序遍歷,刪除結點的左邊則是它的前驅,右邊則是它的後繼,刪除它,可以用它的前驅或者後繼來替代它的位置,那麼怎麼在二叉樹中找到它的前驅結點或者後繼結點呢?
前驅結點的尋找
:根據二叉搜索樹的性質知道,某結點的前驅結點爲其左子樹的右結點的右結點的右結點….右結點;
後繼節點的尋找
:根據二叉搜索樹的性質知道,某結點的後繼節點爲其右子樹的左結點的左結點的左結點…左結點;
根據上面的動態圖,可以知道後繼結點如何替換刪除結點:後繼結點的右結點爲後繼結點父結點的左孩子(後繼結點不存在左結點),刪除結點的左結點爲後繼結點的左孩子,後繼結點爲刪除結點父結點的左結點
。
Java代碼獲取前驅結點和後繼結點:
/**
* 尋找前驅結點
* @param delNode
* @return
*/
public TreeNode<Integer> getPrecursorNode(TreeNode<Integer> delNode) {
TreeNode<Integer> currentNode = delNode.leftNode;
TreeNode<Integer> precursorNode = delNode;
TreeNode<Integer> precursorParentNode = delNode;
while(currentNode!=null) {
precursorParentNode = precursorNode;
precursorNode = currentNode;
currentNode = currentNode.rightNode;
}
// 前驅結點爲刪除結點左子樹的右結點...右結點
if (delNode.leftNode != precursorNode) {
precursorParentNode.rightNode = precursorNode.leftNode;
precursorNode.leftNode = delNode.leftNode;
}
return precursorNode;
}
/**
* 尋找後繼結點
* @param delNode
* @return
*/
public TreeNode<Integer> getSuccessorNode(TreeNode<Integer> delNode) {
TreeNode<Integer> currentNode = delNode.rightNode;
TreeNode<Integer> successorNode = delNode;
TreeNode<Integer> successorParentNode = delNode;
while(currentNode!=null) {
successorParentNode = successorNode;
successorNode = currentNode;
currentNode = currentNode.leftNode;
}
// 後繼結點爲刪除結點右子樹的左結點…左結點
if (delNode.rightNode != successorNode) {
successorParentNode.leftNode = successorNode.rightNode;
successorNode.rightNode = delNode.rightNode;
}
return successorNode;
}
刪除結點的Java代碼:
/**
* 刪除結點
* @param value
* @return
*/
public boolean deleteNode(Integer value) {
if (rootNode == null) return false;
TreeNode<Integer> currentNode = rootNode;
TreeNode<Integer> parentNode = rootNode;
boolean isLeftNode = false;
// 找到要刪除的結點
while (!value.equals(currentNode.nodeValue)) {
parentNode = currentNode;
if (value > parentNode.nodeValue) {
currentNode = parentNode.rightNode;
isLeftNode = false;
} else if (value < parentNode.nodeValue) {
currentNode = parentNode.leftNode;
isLeftNode = true;
}
if (currentNode == null) {
return false;
}
}
// currentNode即爲要刪除的結點 處理四種情況
// 當前結點爲葉子結點
if (currentNode.leftNode == null && currentNode.rightNode == null) {
if (currentNode == rootNode) {
rootNode = null;
} else {
if (isLeftNode) {
currentNode.leftNode = null;
} else {
currentNode.rightNode = null;
}
}
return true;
} else if (currentNode.leftNode == null) { // 刪除結點左結點爲空
if (currentNode == rootNode) {
rootNode = currentNode.rightNode;
} else {
if (isLeftNode) {
parentNode.leftNode = currentNode.rightNode;
} else {
parentNode.rightNode = currentNode.rightNode;
}
}
return true;
} else if (currentNode.rightNode == null) { // 刪除結點右結點爲空
if (currentNode == rootNode) {
rootNode = currentNode.leftNode;
} else {
if (isLeftNode) {
parentNode.leftNode = currentNode.leftNode;
} else {
parentNode.rightNode = currentNode.leftNode;
}
}
return true;
} else { // 刪除結點含有左右結點
TreeNode<Integer> successorNode = getSuccessorNode(currentNode);
if (currentNode == rootNode) {
rootNode = successorNode;
} else {
if (isLeftNode) {
parentNode.leftNode = successorNode;
} else {
parentNode.rightNode = successorNode;
}
}
successorNode.leftNode = currentNode.leftNode;
return true;
}
}
8 源碼地址
github地址