不能忘本,要經常寫點算法
今天來個經典的二叉排序樹
也叫二叉搜索樹,兩年前的記憶有點模糊
有事還得問度娘(下面是鏈接)
二叉排序樹
簡單用遞歸定義下,可以這樣描述
一顆二叉排序樹是滿足下列性質的二叉樹:
(1)若它的左子樹不爲空,則其左子樹上任意結點的關鍵字的值都小於根結點關鍵字的值。
(2)若它的右子樹不爲空,則其右子樹上任意結點的關鍵字的值都大於根節點關鍵字的值。
(3)它的左、右子樹本身又是一個二叉查找樹。
二叉排序樹應該是一顆平衡二叉樹(好像是這麼定義的),意思就是左右節點差不多。
然後他的搜索性能貌似是趨近二分查找(寫到這我就想到0。618比二分快)
但是因爲對數據操作比較方便,比如插入刪除數據,不會做太大的變化,相比連續存儲空間,要少操作很大一段的數據。所以還是很好用的(雖然我現在也沒用到,可能是個假的程序員吧)。
節點的數據結構可以定義爲
package bstSearch;
/**
* Created by zhangguanlong on 2017/12/14.
*/
/**
* @function 二叉搜索樹中的節點
*/
public class Node {
//存放節點數據
int data;
//指向左子節點
Node left;
//指向右子節點
Node right;
/**
* @function 默認構造函數
* @param data 節點數據
*/
public Node(int data) {
this.data = data;
left = null;
right = null;
}
}
查找
而二叉查找樹的查找過程爲從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中;否則,如果查詢關鍵字比結點關鍵字小,就進入左子樹;如果比結點關鍵字大,就進入右子樹;如果左子樹或右子樹的指針爲空,則報告找不到相應的關鍵字。
插入
把一個新的記錄R插入到二叉查找樹,應該保證在插入之後不破壞二叉查找樹的結構性質。因此,爲了執行插入操作首先應該查找R所在的位置。查找時,仍然採用上述的遞歸算法。若查找失敗,則把包含R的結點插在具有空子樹位置,若查找成功,則不執行插入,操作結束。
刪除
刪除不太好描述
借用一下大神們畫的圖 刪葉子節點 內部節點
內部節點
後兩幅應該是要刪18這個節點,這是有左右根節點的子節點
要在子樹中選擇一個合適的替換節點,替換節點一般來說會是右子樹中的最小的節點。
然後給出樹結構代碼,寫註釋是個好習慣,畢竟菜呢
package bstSearch;
/**
* Created by zhangguanlong on 2017/12/14.
*/
/**
* @function 二叉搜索樹的示範代碼
*/
public class BinarySearchTree {
//指向二叉搜索樹的根節點
private Node root;
//默認構造函數
public BinarySearchTree() {
this.root = null;
}
/**
* @param id 待查找的值
* @return
* @function 默認搜索函數
*/
public boolean find(int id) {
//從根節點開始查詢
Node current = root;
//當節點不爲空
while (current != null) {
//是否已經查詢到
if (current.data == id) {
return true;
} else if (current.data > id) {
//查詢左子樹
current = current.left;
} else {
//查詢右子樹
current = current.right;
}
}
return false;
}
/**
* @param id
* @function 插入某個節點
*/
public void insert(int id) {
//創建一個新的節點
Node newNode = new Node(id);
//判斷根節點是否爲空
if (root == null) {
root = newNode;
return;
}
//設置current指針指向當前根節點
Node current = root;
//設置父節點爲空
Node parent = null;
//遍歷直到找到第一個插入點
while (true) {
//先將父節點設置爲當前節點
parent = current;
//如果小於當前節點的值
if (id < current.data) {
//移向左節點
current = current.left;
//如果當前節點不爲空,則繼續向下一層搜索
if (current == null) {
parent.left = newNode;
return;
}
} else {
//否則移向右節點
current = current.right;
//如果當前節點不爲空,則繼續向下一層搜索
if (current == null) {
parent.right = newNode;
return;
}
}
}
}
/**
* @param id
* @return
* @function 刪除樹中的某個元素
*/
public boolean delete(int id) {
Node parent = root;
Node current = root;
//記錄被找到的節點是父節點的左子節點還是右子節點
boolean isLeftChild = false;
//循環直到找到目標節點的位置,否則報錯
while (current.data != id) {
parent = current;
if (current.data > id) {
isLeftChild = true;
current = current.left;
} else {
isLeftChild = false;
current = current.right;
}
if (current == null) {
return false;
}
}
//如果待刪除的節點沒有任何子節點
//直接將該節點的原本指向該節點的指針設置爲null
if (current.left == null && current.right == null) {
if (current == root) {
root = null;
}
if (isLeftChild == true) {
parent.left = null;
} else {
parent.right = null;
}
}
//如果待刪除的節點有一個子節點,且其爲左子節點
else if (current.right == null) {
//判斷當前節點是否爲根節點
if (current == root) {
root = current.left;
} else if (isLeftChild) {
//掛載到父節點的左子樹
parent.left = current.left;
} else {
//掛載到父節點的右子樹
parent.right = current.left;
}
} else if (current.left == null) {
if (current == root) {
root = current.right;
} else if (isLeftChild) {
parent.left = current.right;
} else {
parent.right = current.right;
}
}
//如果待刪除的節點有兩個子節點
else if (current.left != null && current.right != null) {
//尋找右子樹中的最小值
Node successor = getSuccessor(current);
if (current == root) {
root = successor;
} else if (isLeftChild) {
parent.left = successor;
} else {
parent.right = successor;
}
successor.left = current.left;
}
return true;
}
/**
* @param deleleNode
* @return
* @function 在樹中查找最合適的節點
*/
private Node getSuccessor(Node deleleNode) {
Node successsor = null;
Node successsorParent = null;
Node current = deleleNode.right;
while (current != null) {
successsorParent = successsor;
successsor = current;
current = current.left;
}
if (successsor != deleleNode.right) {
successsorParent.left = successsor.right;
successsor.right = deleleNode.right;
}
return successsor;
}
/**
* @function 以中序順序遍歷樹
*/
public void display() {
display(root);
}
private void display(Node node) {
//判斷當前節點是否爲空
if (node != null) {
//首先展示左子樹
display(node.left);
//然後展示當前根節點的值
System.out.print(" " + node.data);
//最後展示右子樹的值
display(node.right);
}
}
}
測試代碼
package bstSearch;
import org.junit.Before;
import org.junit.Test;
/**
* Created by zhangguanlong on 2017/12/14.
*/
public class BinarySearchTreeTest {
BinarySearchTree binarySearchTree;
@Before
public void setUp() {
binarySearchTree = new BinarySearchTree();
binarySearchTree.insert(3);
binarySearchTree.insert(8);
binarySearchTree.insert(1);
binarySearchTree.insert(4);
binarySearchTree.insert(6);
binarySearchTree.insert(2);
binarySearchTree.insert(10);
binarySearchTree.insert(9);
binarySearchTree.insert(20);
binarySearchTree.insert(25);
binarySearchTree.insert(15);
binarySearchTree.insert(16);
System.out.println("原始的樹 : ");
binarySearchTree.display();
System.out.println("");
}
@Test
public void testFind() {
System.out.println("判斷4是否存在樹中 : " + binarySearchTree.find(4));
}
@Test
public void testInsert() {
}
@Test
public void testDelete() {
System.out.println("刪除值爲2的節點 : " + binarySearchTree.delete(2));
binarySearchTree.display();
System.out.println("\n 刪除有一個子節點值爲4的節點 : " + binarySearchTree.delete(4));
binarySearchTree.display();
System.out.println("\n 刪除有兩個子節點的值爲10的節點 : " + binarySearchTree.delete(10));
binarySearchTree.display();
}
}
還可以吧,長夜漫漫,陪伴我的只有代碼和音樂
好了,不耍(zhuang)帥(bi)了,其實還沒下班。。。