看圖說話之二叉排序樹
二叉排序樹
一丶二叉排序樹基本介紹
二叉樹是一種常見的樹結構,二叉排序樹是二叉樹的一種特殊情況,其在二叉樹的基礎上施加了一種“順序性”的約束,BST在排序和查詢中具有廣泛的運用下圖就是一個常見的BST(Binary sort tree,二叉排序樹 )。
圖1 二叉排序樹
對照圖1所示的二叉排序樹,可以給出BST的如下主要特點:
(1)結構上,BST是二叉樹
(2)排序性上,對於BST的任意一個節點,節點值大於左子樹上的所有節點值,小於右子樹上的所有節點值。
從上述BST兩條主要的特點可以看出BST是遞迴定義的,其左子樹和右子樹均是BST樹。瞭解了BST的基本特點之後,接下來總結BST的插入,刪除,建樹的基本操作原理。
二丶BST的插入操作
假設待插入陣列為: int [] A =new int []{1, 17},將該陣列一次插入到圖1所示的二叉排序樹中(這次做一個特殊的說明,在插入資料中不存在重複的資料)。
1.插入元素1,首先和根節點比較,發現1比根節點8小, 接著和節點8的左節點比較,發現1還是小於7,接著繼續和7的左節點6比較,1小於6,同時節點6左節點為null,不存在比較的可能了,此時節點1充當節點6的左節點,比較的過程和結果如下圖所示。
圖2元素1的插入過程
2.在上圖2的基礎上繼續插入元素17,重複和步驟1相同的過程。首先17和根節點9比較,17大於9,所以和節點9的右節點比較,17>15,繼續和15的右節點比較發現,17<18,所以17和18的左節點比較,但是發現18的左節點為null,於是17找到了正確的位置,為了18的左節點。比較的過程和結果如下圖所示
圖3元素17的插入過程
依據上述的兩個例子可以清晰的看到BST的插入過的基本實現,下面總結下BST插入過程的特點。
(1)插入過程是一個遞迴的過程,用遞迴的過程可以這樣描述,如果元素小於根元素,則將元素插入左子樹,如果元素大於根元素,則將元素插入右子樹,如果子樹為
null,那麼插入元素就是新的子樹。利用虛擬碼可以如下實現。
public Nodeinsert(element t,Node root){ if(root==null) new Node(t); if(t<root.element){ root.left= insert(t,root.left); }else if(t>root.element){ root.right =insert(t,root.right); }else { //在BST中找到了相等的節點,不做處理直接返回 return ; } }
(2)BST樹的插入過程的時間效率取決於查詢過程中元素的比較次數,被查詢元素的深度越深時間消耗越大,最壞的情況就是插入序列是排序的情況,這樣樹的深度是N,插入的時間消耗也是O(N),如果BST的樹形是完全二叉樹,那麼其最大深度就是Log(N),那麼插入序列的時間消耗就是Log(N)。最壞情況和最優情況圖下圖所示:
圖4 最優BST樹形結構和最差BST樹形
三丶BST樹的刪除操作
BST樹的刪除操作相比較於BST樹的插入操作,過程很相似,只是稍稍複雜一些,主要是分如下三種情況進行討論。
(1)被刪除元素不存在左兒子和右兒子,其過程如下圖所示
、
圖5定位被刪除節點
圖6 刪除節點
在該種情況下被刪除元素不存在兒子節點,直接用null代替被刪除元素
(2)被刪除元素只存在一個兒子,其過程如下 圖所示
圖7定位被刪除節點
圖8刪除節點
在該種情況下,被刪除節點只有一個兒子,直接由其兒子節點替代刪除節點。
(3)被刪除節點有兩個兒子,其主要過程如下所示三步完成。
圖9 定位被刪除節點
圖10刪除第二步
如圖9中所示,定位被刪除節點後,如果該節點左右兒子都存在,那麼找到該節點右子樹中的最小節點,用該最小節點替代被刪除節點
圖11 刪除節點右子樹最小節點。
經過圖8,圖9,圖10所表示的不步驟後,元素6被刪除了且BST的順序性並未改變。上述三個步驟的彙總如下圖所示
圖12 被刪除節點具有左兒子和右兒子的刪除操作
(1)小小的討論,當被刪除節點沒有兒子或者只存在一個兒子時的刪除操作不會存在任何疑問,但是當被刪除節點存在兩個兒子時的刪除操作存在一個問題:為了必須用右子樹的最小節點來替代,不能用左子樹的最大節點來替代嗎?答案是可以的,這兩種刪除的方式隨便選擇一個。對於一個棵很大BST而言,如果採用右子樹的刪除操作,經過多次刪除操作後,BST的樹形可能偏向左,同理相反。
(2)二叉樹刪除操作是遞迴的實現,用java可以這樣實現。
public Node delete(int element,Node root){
//未定位到刪除元素
if(root==null) return null;
intdelta = element-root.element;
if(delta<0){
root.left = delete(element,root.left);
}elseif(delta>0){
root.right =delete(element,root.right);
}else{
if(root.left!=null&&root.right!=null){
Node node = findMin(root.right);
root.element =node.element;
root.right = delete(node.element,root.right);
}else{
root = root.left!=null?root.left:root.right;
}
}
returnroot;
}
四丶二叉樹的建樹操作。
二叉樹的建樹操作很容易理解,建立一個N個節點BST操作,就是對元素執行N次的插入操作,每次插入操作的時間複雜度若為理想得Log(N),那麼建樹操作的時間複雜度就是Nlog(N)。
五丶BST的應用
(1)BST如其名字可以用來進行排序,利用待排序陣列建立一個BST樹,假設建立結果如下。中序遍歷該陣列元素輸出就是對陣列排序的結果
圖13 BST
中序遍歷結果: 6->7->8->9->10->12->15->18
建樹的時間複雜度是Nlog(N),中序遍歷額時間複雜度是N,綜合起來該種排序方法時間複雜度是Nlog(N)。
(2)BST樹可以用來查詢,BST的查詢操作就是刪除操作中定位操作,時間複雜度是log(N)。
這次要特別注意,上述的log(N)的時間複雜度是建立在BST具有如圖5所示的良好樹形結構的前提下。
六丶BST樹的java程式碼實現
public class BinarySortTree {
publicNode root = null;
publicstatic void main(String []args){
int[] elements = new int[]{5,4,9,3,2};
BinarySortTree bst = new BinarySortTree();
/****測試插入,建樹和中序遍歷操作***/
bst.buildTree(elements);
bst.midTrace();
System.out.println();
/****測數刪除操作之無兒子***************/
// bst.delete(9);
// bst.midTrace();
/****測數刪除操作之只有一個兒子***************/
// bst.delete(4);
// bst.midTrace();
/****測數刪除操作只有兩個兒子***************/
bst.delete(5);
bst.midTrace();
}
//節點型別定義
publicstatic class Node{
publicint element;
publicNode left;
publicNode right;
publicNode(int element){
this.element = element;
left = null;
right = null;
}
}
//外部使用的插入方法
publicvoid insert(int element){
root=insert(element,root);
}
//內部使用的插入方法
private Node insert(int element,Node root){
if(root==null)
return new Node(element);
intdelta = element-root.element;
if(delta<0){
root.left = insert(element,root.left);
}elseif(delta>0){
root.right = insert(element,root.right);
}else{
//不做處理
}
returnroot;
}
//外部使用的建樹方法
publicvoid buildTree(int [] elements){
if(root==null){
for(int i=0;i<elements.length;i++){
insert(elements[i]);
}
}else{
//若樹以存在,則不能建
}
}
publicvoid delete(int element){
delete(element,root);
}
publicNode delete(int element,Node root){
//未定位到刪除元素
if(root==null) return null;
intdelta = element-root.element;
if(delta<0){
root.left = delete(element,root.left);
}elseif(delta>0){
root.right =delete(element,root.right);
}else{
if(root.left!=null&&root.right!=null){
Node node = findMin(root.right);
root.element =node.element;
root.right = delete(node.element,root.right);
}else{
root = root.left!=null?root.left:root.right;
}
}
returnroot;
}
//中序遍歷二叉樹
publicvoid midTrace(){
if(root!=null){
print(root);
}
}
privatevoid print(Node node){
if(node!=null){
print(node.left);
System.out.print(node.element+" ");
print(node.right);
}
}
//找到該節點下子樹的最小節點。
publicNode findMin(Node node){
if(node.left==null)
return node;
else
return findMin(node.left);
}
}