資料機構與演算法:二叉查詢樹(Binary Search Tree)Java實現
個人總結,如有錯誤,感謝指正
二叉查詢樹(Binary Search Tree)
一、簡介
二叉樹(Binary Tree):每個節點最多有兩個子節點的樹。
二叉查詢樹(binary srarch tree):具有如下性質的二叉樹稱為二叉查詢樹:
- 對樹中任意節點A,若左子樹不空,則左子樹中所有節點的值比A的值小
- 對樹中任意節點A,若右子樹不空,則右子樹中所有節點的值比A的值大
沒有鍵值相等的的節點
如下結構是一個二叉查詢樹:
對於動態陣列(ArrayList)與連結串列(LinkedList)結構,動態陣列在查詢方面效率較高,連結串列結構在插入方面效率較高,而二叉查詢樹融合了上面兩者的優勢。
二、實現
下面用Java實現二叉查詢樹,包含二叉查詢樹定義、前序/中序/後序遍歷、查詢、最大值、最小值、插入、刪除。
定義
用一個內部類定義二叉樹節點BinaryNode,它包含指向左右兩個子節點的left、right,以及表示節點值的key,key需要支援排序。
public class BinarySearchTree<T extends Comparable<T>> {
//二叉查詢樹根節點
private BinaryNode<T> root;
//二叉樹節點定義
private static class BinaryNode<T extends Comparable<T>> {
T key; //節點值
BinaryNode<T> left; //左節點
BinaryNode<T> right; //右節點
public BinaryNode(T key){
this(key, null, null);
}
public BinaryNode(T key, BinaryNode<T> left, BinaryNode<T> right) {
this .key = key;
this.left = left;
this.right = right;
}
}
...
}
遍歷
二叉查詢樹遍歷包含前序、中序、後序遍歷。這裡的“前、中、後”都是針對“當前節點”而言,遍歷順序分別如下:
前序遍歷:當前節點、左節點、右節點;
中序遍歷:左節點、當前節點、右節點;
後序遍歷:左節點、右節點、當前節點。
遍歷每個節點都按上述順序進行。
/**
* 前序遍歷
* @param node 待遍歷二叉查詢樹BST根節點
*/
private void preOrder(BinaryNode<T> node) {
if (node != null) {
System.out.print(node.key + " ");
preOrder(node.left);
preOrder(node.right);
}
}
/**
* 前序遍歷
*/
public void preOrder() {
preOrder(this.root);
}
/**
* 中序遍歷
* @param node 待遍歷BST根節點
*/
private void midOrder(BinaryNode<T> node) {
if (node != null) {
midOrder(node.left);
System.out.print(node.key + " ");
midOrder(node.right);
}
}
/**
* 中序遍歷
*/
public void midOrder() {
midOrder(this.root);
}
/**
* 後序遍歷
* @param node 待遍歷BST根節點
*/
private void postOrder(BinaryNode<T> node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.print(node.key + " ");
}
}
/**
* 後序遍歷
*/
public void postOrder() {
postOrder(this.root);
}
查詢
在二叉查詢樹BST中查詢值為key的節點,將key和節點的key進行比較,如果小於,則在左子樹中查詢,如果大於,則在右子樹中查詢,如果等於,則查詢成功。下面是遞迴和非遞迴兩種實現方式:
/**
* (遞迴實現)在root為根節點的二叉樹中查詢節點值為key的節點
* @param root
* @param key
* @return
*/
private BinaryNode<T> search(BinaryNode<T> root, T key) {
if (root == null) {
return null;
}
//如果key小於當前節點值,則在左樹中查詢
if (key.compareTo(root.key) < 0) {
return search(root.left, key);
//如果key小於當前節點值,則在左樹中查詢
} else if (key.compareTo(root.key) > 0) {
return search(root.right, key);
//如果key等於當前節點值,直接返回
} else {
return root;
}
}
/**
* (非遞迴實現)在root為根節點的二叉樹中查詢節點值為key的節點
* @param root
* @param key
* @return
*/
private BinaryNode<T> cycleSearch(BinaryNode<T> root, T key) {
while (root != null) {
//如果key小於當前節點值,則在左樹中查詢
if (key.compareTo(root.key) < 0) {
root = root.left;
//如果key小於當前節點值,則在左樹中查詢
} else if (key.compareTo(root.key) > 0) {
root = root.right;
//如果key等於當前節點值,直接返回
} else {
return root;
}
}
return root;
}
/**
* 查詢
*/
public BinaryNode<T> search(T key) {
return search(this.root, key);
//return cycleSearch(this.root, key);
}
最小值、最大值
因為二叉查詢樹左子樹的值都比根節點小,右子樹的值都比根節點大,所以最小值即為最左邊節點的值,最大值即為最右邊節點的值。
/**
* 查詢最小值,即最左邊節點值
*/
private T findMin(BinaryNode<T> root){
if(root == null){
return null;
}
while(root.left != null){
root = root.left;
}
return root.key;
}
/**
* 最小值
*/
public T findMin(){
return findMin(this.root);
}
/**
* 查詢最大值,即查詢最右節點值
*/
private T findMax(BinaryNode<T> root){
if(root == null){
return null;
}
while(root.right != null){
root = root.right;
}
return root.key;
}
/**
* 最大值
*/
public T findMax(){
return findMax(this.root);
}
插入
在二叉查詢樹中插入新節點newNode,將新節點的值和當前節點值進行比較,如果小於,在左邊插入,如果大於,在右邊插入。
/**
* 在二叉查詢樹bst中插入新節點newNode
* @param bst
* @param newNode
* @return
*/
private boolean insert(BinarySearchTree<T> bst, BinaryNode<T> newNode){
//如果root為空,則新節點作為根節點
BinaryNode<T> root = bst.root;
if(root == null){
bst.root = newNode;
return true;
}
while(root != null){
int cmp = newNode.key.compareTo(root.key);
//小於,在左邊插入
if(cmp < 0){
if(root.left == null){
root.left = newNode;
return true;
}else{
root = root.left;
}
//大於,在右邊插入
}else if(cmp > 0){
if(root.right == null){
root.right = newNode;
return true;
}else{
root = root.right;
}
//等於,則插入失敗,返回false
}else{
return false;
}
}
return false;
}
/**
* 插入
*/
public boolean insert(BinaryNode<T> newNode){
return insert(this, newNode);
}
刪除
從二叉查詢樹中刪除節點時,分三種情況:
- 待刪除節點為葉節點,則直接刪除
- 待刪除節點只有一個子節點,則用子節點的值代替該節點的值,然後刪除該子節點
- 待刪除節點有兩個子節點,這種情況相對比較負責,一般用其右子樹中最小節點的值替代該節點值,然後刪除該最小節點。
懶惰刪除:當需要從樹中刪除某個節點時,並不會真的將該節點從樹中移除,而是仍留在樹中,只是被標記為已刪除,這樣在對樹進行操作時,遇到被標記為已刪除的節點時,直接跳過。這樣,可以提高刪除操作效率。
/**
* 從二叉查詢樹bst中刪除值為key的節點
* @param bst
* @param key
* @return
*/
private boolean remove(BinarySearchTree<T> bst, T key){
BinaryNode<T> tRoot = bst.root;
if(tRoot == null){
return false;
}
BinaryNode<T> pNode = tRoot;
BinaryNode<T> rmNode = tRoot;
//是否左節點
boolean isLeft = false;
while(rmNode != null){
int cmp = key.compareTo(rmNode.key);
if(cmp < 0){
isLeft = true;
pNode = rmNode;
rmNode = rmNode.left;
} else if(cmp > 0){
isLeft = false;
pNode = rmNode;
rmNode = rmNode.right;
} else{
//葉節點,直接刪除
if(rmNode.left == null && rmNode.right == null){
if(rmNode == tRoot){
tRoot = null;
}else if(isLeft){
pNode.left = null;
}else{
pNode.right = null;
}
}
//只有左節點
else if(rmNode.left != null && rmNode.right == null){
if(rmNode == tRoot){
tRoot = null;
}else if(isLeft){
pNode.left = rmNode.left;
}else{
pNode.right = rmNode.left;
}
}
//只有右節點
else if(rmNode.left == null && rmNode.right != null){
if(rmNode == tRoot){
tRoot = null;
}else if(isLeft){
pNode.left = rmNode.right;
}else{
pNode.right = rmNode.right;
}
}
//兩個節點
else if(rmNode.left != null && rmNode.right != null){
//被刪除節點右子樹沒有左節點,直接用右節點替換
if(rmNode.right.left == null){
rmNode.key = rmNode.right.key;
rmNode.right = rmNode.right.right;
return true;
}
//右子樹最小節點
BinaryNode<T> currNode = rmNode.right;
//替代節點
BinaryNode<T> replaceNode = rmNode.right;
//替代節點父節點
BinaryNode<T> repParent = rmNode.right;
while(currNode != null){
repParent = replaceNode;
replaceNode = currNode;
currNode = currNode.left;
}
repParent.left = replaceNode.right;
rmNode.key = replaceNode.key;
if(rmNode == tRoot){
tRoot = null;
}
}
return true;
}
}
return false;
}
/**
* 刪除
*/
public boolean remove(T key){
return this.remove(this, key);
}
測試
呼叫上面的實現方法進行測試,使用陣列 [8,3,10,6,7,9,17,2,53,23] 作為二叉查詢樹資料:
public static void main(String[] args){
Integer[] intArr = new Integer[]{8,3,10,6,7,9,17,2,53,23};
BinarySearchTree<Integer> bst = new BinarySearchTree<Integer>();
//構造二叉樹,迴圈插入資料
for(int num : intArr){
bst.insert(new BinaryNode<Integer>(num));
}
System.out.println("前序遍歷:");
bst.preOrder();
System.out.println("\n中序遍歷:");
bst.midOrder();
System.out.println("\n後序遍歷:");
bst.postOrder();
System.out.println("\n最大值:" + bst.findMax());
System.out.println("最小值:" + bst.findMin());
System.out.println("查詢6:" );
BinaryNode<Integer> node6 = bst.search(6);
System.out.println(node6 == null ? "" : node6.key);
System.out.println("查詢33:" );
BinaryNode<Integer> node33 = bst.search(33);
System.out.println(node33 == null ? "" : node33.key);
System.out.println("刪除2:" );
bst.remove(2);
//System.out.println("刪除6:" );
//bst.remove(6);
//System.out.println("刪除10:" );
//bst.remove(10);
bst.midOrder();
}
使用陣列[8,3,10,6,7,9,17,2,53,23] 構造二叉查詢樹如下:
測試結果
前序遍歷:
8 3 2 6 7 10 9 17 53 23
中序遍歷:
2 3 6 7 8 9 10 17 23 53
後序遍歷:
2 7 6 3 9 23 53 17 10 8
最大值:53
最小值:2
查詢6:
6
查詢33:
沒有找到值為33的節點
刪除2:
3 6 7 8 9 10 17 23 53
刪除6:
2 3 7 8 9 10 17 23 53
刪除10:
2 3 6 7 8 9 17 23 53
以上就是二叉查詢樹及其Java實現,如有錯誤,感謝指正。