爲什麼使用二叉排序樹?
我們在存儲數據時,需要考慮其增刪改查的速度。例如未排序的數組添加很快,直接在尾部添加,但是查詢很慢;排序好的數組再添加數據時需要找到添加的位置再將後面的數據全部移動,但是檢索速度很快,可以用二分查找方法;而鏈表結構存儲數據插入很快,但是檢索也比較慢,所以爲了提高檢索速度並且不減慢添加速度,我們就需要用到二叉排序樹來解決。
1、二叉排序樹
二叉排序樹(Binary Sort Tree),又稱二叉查找樹(Binary Search Tree)、二叉搜索樹。對於二叉排序樹的任何一個非葉子節點,要求左子節點的值比當前節點的值小,右子節點的值比當前節點的值大。如果有相同的值,可以將該節點放在左子節點或右子節點。
例如:我們有一個數組{3 ,12,9, 1,6, 20,16} ,將其構建成一個二叉排序樹如下圖:接下來我們用代碼實現二叉排序樹的創建和遍歷。
1.1 二叉排序樹的創建和遍歷
首先創建我們的節點類,裏面創建節點有關的屬性:節點值,節點的左子節點和右子節點屬性。還有我們編寫遍歷方法,幫助我們遍歷創建好的二叉排序樹,這裏需要注意的是:當我們創建好了一顆二叉排序樹,對這棵樹進行中序遍歷,得到的結果就是一個有序序列,這是一個二叉排序樹的特點,我們創建好進行遍歷來觀察。
public class Node {
int value;
Node left;
Node right;
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
public Node(int value) {
this.value = value;
}
//添加節點(以遞歸的形式爲二叉排序樹添加節點)
public void add(Node node) {
if (node == null) {
return;
}
//判斷傳入的節點值,和當前子樹的根節點的值關係
if (node.value < this.value) {//此節點需要添加根節點的左邊
if (this.left == null) {
this.left = node;
} else {//否則遞歸向左子樹添加
this.left.add(node);
}
} else {//此節點需要添加根節點的右邊
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
}
//前序遍歷
public void preOrder() {
System.out.println(this);
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
//中序遍歷
public void midOrder() {
if (this.left != null) {
this.left.midOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.midOrder();
}
}
//後續遍歷
public void postOrder() {
if (this.left != null) {
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
}
然後我們創建二叉排序樹,將調用我們節點中的add方法進行創建二叉樹方法的編寫。
public class BinarySortTree {
private Node root;
//添加節點方法
public void add(Node node) {
if (root == null) {
root = node;
} else {
root.add(node);
}
}
//中序遍歷
public void midOrder() {
if (root != null) {
root.midOrder();
} else {
System.out.println("樹爲空!,無法遍歷");
}
}
}
然後我們進行測試類Test的編寫
public class Test {
public static void main(String[] args) {
int[] arr = {3, 12, 9, 1, 6, 20, 16};
BinarySortTree binarySortTree = new BinarySortTree();
for (int i = 0; i < arr.length; i++) {
binarySortTree.add(new Node(arr[i]));
}
System.out.println("中序遍歷二叉排序樹");
binarySortTree.midOrder();
}
}
運行我們可以看到前序遍歷得到的結果是一個有序的數列,這也可以證明我們的二叉排序樹構建成功。
中序遍歷二叉排序樹
Node{value=1}
Node{value=3}
Node{value=6}
Node{value=9}
Node{value=12}
Node{value=16}
Node{value=20}
Process finished with exit code 0
1.2 二叉排序樹的刪除(節點)
二叉排序樹的刪除操作分爲三種情況:
(1)要刪除的節點是葉子節點:如果我們要刪除的節點是葉子節點,我們就需要找到該節點及其父結點,然後判斷該節點是其父結點的左子節點還是右子節點,判斷之後,如果是其父結點的左子節點,我們就讓其父結點的左指針置爲空即可,反之亦然。這樣就可以刪除這個葉子節點。
(2)要刪除的節點有一個子節點:如果我們要刪除的節點有一個子節點,我們首先還是要找到該節點,再找到其父結點,判斷該節點是其父節點的左子節點還是右子節點,然後再判斷該節點的子節點是其左子節點還是右子節點。這時我們需要分四步來完成:
①如果要刪除的節點是其父結點的左子節點,並且他有左子節點,則需要將其父節點的左指針指向其左子節點即可。
②如果要刪除的節點是其父結點的左子節點,並且他有右子節點,則需要將其父節點的左指針指向其右子節點即可。
③如果要刪除的節點是其父結點的右子節點,並且他有左子節點,則需要將其父節點的右指針指向其左子節點即可。
④如果要刪除的節點是其父結點的右子節點,並且他有右子節點,則需要將其父節點的右指針指向其右子節點即可。
(3)要刪除的節點有倆個子節點:如果我們要刪除的節點有倆個子節點,我們首先還需要找到該節點以及該節點的父結點,從要刪除的節點的右子樹中找一個最小的節點放到要刪除的節點上,刪除這個最小的節點即可完成。
接下來我們用代碼實現:首先node節點中定義我們的刪除操作
//查找要刪除的節點
public Node search(int value) {
if (value == this.value) {//找到要刪除節點
return this;
} else if (value < this.value) {
if (this.left == null) {
return null;
}
return this.left.search(value);
} else {
if (this.right == null) {
return null;
}
return this.right.search(value);
}
}
//查找要刪除節點的父結點
public Node searchParent(int value) {
//說明當前節點就是要刪除節點的父結點,就返回該值即可
if ((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)) {
return this;
} else {
//如果查找的值比當前節點的小,則向左遞歸查找
if (value < this.value && this.left != null) {
return this.left.searchParent(value);
} else if (value >= this.value && this.right != null) {
return this.right.searchParent(value);
} else {
return null;
}
}
}
然後我們在BinarySortTree 類中進行刪除方法的編寫:
//查找要刪除的節點
public Node search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
//查找要刪除節點的父結點
public Node searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
//刪除節點
public void delNde(int value) {
if (root == null) {
return;
} else {
Node targetNode = search(value);
if (targetNode == null) {
return;
}
//如果發現當前二叉樹只有一個節點
if (root.left == null && root.right == null) {
root = null;
return;
}
//去找targetNode的父結點
Node parent = searchParent(value);
//如果要刪除的是葉子節點
if (targetNode.left == null && targetNode.right == null) {
//判斷targetNode是父結點的左/右子節點
if (parent.left != null && parent.left.value == value) {
parent.left = null;
} else if (parent.right != null && parent.right.value == value) {
parent.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) {//刪除有倆個子樹的節點
int midVal = delRightTreeMid(targetNode.right);
targetNode.value = midVal;
} else {//刪除只有一顆子樹的節點
//如果要刪除的有左子節點
if (parent != null) {
if (targetNode.left != null) {
if (parent.left.value == value) {
parent.left = targetNode.left;
} else {
parent.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else {
if (parent != null) {
if (parent.left.value == value) {
parent.left = targetNode.right;
} else {
parent.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
}
}
//編寫刪除有倆個子節點的節點的輔助方法
/**
* @param node 傳入的節點(當做二叉排序樹的根節點)
* @return 返回的 以node 爲根節點的二叉排序樹的最小節點的值
*/
public int delRightTreeMid(Node node) {
Node target = node;
//循環查找左子節點,找到最小值
while (target.left != null) {
target = target.left;
}
//這時target就指向了最小節點
delNde(target.value);
return target.value;
}
最後我們測試代碼,這裏我就寫一個刪除葉子節點的測試,其他的類似。
public class Test {
public static void main(String[] args) {
int[] arr = {3, 12, 9, 1, 6, 20, 16};
BinarySortTree binarySortTree = new BinarySortTree();
for (int i = 0; i < arr.length; i++) {
binarySortTree.add(new Node(arr[i]));
}
System.out.println("中序遍歷二叉排序樹");
binarySortTree.midOrder();
//刪除葉子節點:1、6、16
binarySortTree.delNde(1);
System.out.println("刪除葉子節點之後,中序遍歷二叉排序樹");
binarySortTree.midOrder();
}
}
中序遍歷二叉排序樹
Node{value=1}
Node{value=3}
Node{value=6}
Node{value=9}
Node{value=12}
Node{value=16}
Node{value=20}
刪除葉子節點之後,中序遍歷二叉排序樹
Node{value=3}
Node{value=6}
Node{value=9}
Node{value=12}
Node{value=16}
Node{value=20}
Process finished with exit code 0