當所有的靜態查找結構添加和刪除一個數據的時候,整個結構都需要重建。這對於常常需要在查找過程中動態改變數據而言,是災難性的。因此人們就必須去尋找高效的動態查找結構,我們在這討論一個非常常用的動態查找樹——二叉查找樹。
二叉查找樹的特點
下面的圖就是兩棵二叉查找樹,我們可以總結一下他的特點:
(1) 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值
(2) 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值
(3) 它的左、右子樹也分別爲二叉查找樹
我們中序遍歷這兩棵樹發現一個有序的數據序列: 【1 2 3 4 5 6 7 8 】
二叉查找樹的操作
插入操作:
現在我們要查找一個數9,如果不存在則,添加進a圖。我們看看二叉查找樹動態添加的過程:
1). 數9和根節點4比較(9>4),則9放在節點4的右子樹中。
2). 接着,9和節點5比較(9>5),則9放在節點5的右子樹中。
3). 依次類推:直到9和節點8比較(9>8),則9放在節點8的右子樹中,成爲節點8的右孩子。
這個過程我們能夠發現,動態添加任何一個數據,都會加在原樹結構的葉子節點上,而不會重新建樹。 由此可見,動態查找結構確實在這方面有巨大的優勢。
刪除操作:
如果二叉查找樹中需要刪除的結點左、右子樹都存在,則刪除的時候需要改變一些子樹結構,但所需要付出的代價很小。
具體的插入,刪除算法請參加《數據結構算法與應用——搜索樹》P5-8。[該章節已經上傳到《查找結構專題(6):動態查找樹比較 》中]。
二叉查找樹的效率分析
那麼我們再來看看二叉查找樹的效率問題
很顯然,在a,b兩圖的二叉查找樹結構中查找一個數據,並不需要遍歷全部的節點元素,查找效率確實提高了。但是有一個很嚴重的問題:我們在a圖中查找8需要比較5次數據,而在B圖中只需要比較3次。更爲嚴重的是:如果按有序序列[1 2 3 4 5 6 7 8]建立一顆二叉查找樹,整棵樹就退化成了一個線性結構(如c輸入圖:單支樹),此時查找8需要比較8次數據,和順序查找沒有什麼不同。
總結一下:最壞情況下,構成的二叉排序樹蛻變爲單支樹,樹的深度爲n,其查找時間複雜度與順序查找一樣O(N)。最好的情況是二叉排序樹的形態和折半查找的判定樹相同,其平均查找長度和log2(N)成正比(O(log2(n)))。
這說明:同樣一組數據集合,不同的添加順序會導致查找樹的結構完全不一樣,直接影響了查找效率。
那麼如何解決這個問題呢? 我們會在下面的專題中:《平衡二叉樹》 中來解決。
下面是java實現的二叉查找樹(說句老實話,沒有指針的java實現數據結構真是複雜):
package net.hr.algorithm.search;
import java.util.ArrayList;
/**
* 二叉樹節點結構
* @author heartraid
*/
class BSTNode<E extends Comparable<E>>{
/**結點關鍵字*/
E key=null;
/**直接父親結點*/
BSTNode<E> parent=null;
/**結點左子樹的根節點*/
BSTNode<E> lchild=null;
/**結點右子樹的根節點*/
BSTNode<E> rchild=null;
BSTNode(E k){
this.key=k;
}
}
/**
* 二叉查找樹 Binary Search Tree(BST)
* @author heartraid
*
*/
public class BST<E extends Comparable<E>> {
/**樹根*/
private BSTNode<E> root=null;
public BST(){
}
/**
* BST 查詢關鍵字
* @param key 關鍵字
* @return 查詢成功/true, 查詢失敗/false
*/
public boolean search(E key){
System.out.print("搜索關鍵字["+key+"]:");
if(key==null||root==null){
System.out.println("搜索失敗");
return false;
}
else{
System.out.print("搜索路徑[");
if(searchBST(root,key)==null){
return false;
}
else return true;
}
}
/**
* BST插入關鍵字
* @param key 關鍵字
* @return 插入成功/true, 插入失敗/false
*/
public boolean insert(E key){
System.out.print("插入關鍵字["+key+"]:");
if(key==null) return false;
if(root==null){
System.out.println("插入到樹根。");
root=new BSTNode<E>(key);
return true;
}
else{
System.out.print("搜索路徑[");
return insertBST(root,key);
}
}
public boolean delete(E key){
System.out.print("刪除關鍵字["+key+"]:");
if(key==null||root==null){
System.out.println("刪除失敗");
return false;
}
else{
System.out.print("搜索路徑[");
//定位到樹中待刪除的結點
BSTNode<E> nodeDel=searchBST(root,key);
if(nodeDel==null){
return false;
}
else{
//nodeDel的右子樹爲空,則只需要重接它的左子樹
if(nodeDel.rchild==null){
BSTNode<E> parent=nodeDel.parent;
if(parent.lchild.key.compareTo(nodeDel.key)==0)
parent.lchild=nodeDel.lchild;
else
parent.rchild=nodeDel.lchild;
}
//左子樹爲空,則重接它的右子樹
else if(nodeDel.lchild==null){
BSTNode<E> parent=nodeDel.parent;
if(parent.lchild.key.compareTo(nodeDel.key)==0)
parent.lchild=nodeDel.rchild;
else
parent.rchild=nodeDel.rchild;
}
//左右子樹均不空
else{
BSTNode<E> q=nodeDel;
//先找nodeDel的左結點s
BSTNode<E> s=nodeDel.lchild;
//然後再向s的右盡頭定位(這個結點將替代nodeDel),其中q一直定位在s的直接父親結點
while(s.rchild!=null){
q=s;
s=s.rchild;
}
//換掉nodeDel的關鍵字爲s的關鍵字
nodeDel.key=s.key;
//重新設置s的左子樹
if(q!=nodeDel)
q.rchild=s.lchild;
else
q.lchild=s.lchild;
}
return true;
}
}
}
/**
* 遞歸查找關鍵子
* @param node 樹結點
* @param key 關鍵字
* @return 查找成功,返回該結點,否則返回null。
*/
private BSTNode<E> searchBST(BSTNode<E> node, E key){
if(node==null){
System.out.println("]. 搜索失敗");
return null;
}
System.out.print(node.key+" —>");
//搜索到關鍵字
if(node.key.compareTo(key)==0){
System.out.println("]. 搜索成功");
return node;
}
//在左子樹搜索
else if(node.key.compareTo(key)>0){
return searchBST(node.lchild,key);
}
//在右子樹搜索
else{
return searchBST(node.rchild,key);
}
}
/**
* 遞歸插入關鍵字
* @param node 樹結點
* @param key 樹關鍵字
* @return true/插入成功,false/插入失敗
*/
private boolean insertBST(BSTNode<E> node, E key){
System.out.print(node.key+" —>");
//在原樹中找到相同的關鍵字,無需插入。
if(node.key.compareTo(key)==0)
{
System.out.println("]. 搜索有相同關鍵字,插入失敗");
return false;
}
else{
//搜索node的左子樹
if(node.key.compareTo(key)>0){
//如果當前node的左子樹爲空,則將新結點key node插入到左孩子處
if(node.lchild==null) {
System.out.println("]. 插入到"+node.key+"的左孩子");
BSTNode<E> newNode=new BSTNode<E>(key);
node.lchild=newNode;
newNode.parent=node;
return true;
}
//如果當前node的左子樹存在,則繼續遞歸左子樹
else return insertBST(node.lchild, key);
}
//搜索node的右子樹
else{
if(node.rchild==null){
System.out.println("]. 插入到"+node.key+"的右孩子");
BSTNode<E> newNode=new BSTNode<E>(key);
node.rchild=newNode;
newNode.parent=node;
return true;
}
else return insertBST(node.rchild,key);
}
}
}
/**
* 得到BST根節點
* @return BST根節點f
*/
public BSTNode<E> getRoot(){
return this.root;
}
/**
* 非遞歸中序遍歷BST
*/
public void InOrderTraverse(){
if(root==null)
return;
BSTNode<E> node=root;
ArrayList<BSTNode<E>> stack=new ArrayList<BSTNode<E>>();
stack.add(node);
while(!stack.isEmpty()){
while(node.lchild!=null){
node=node.lchild;
stack.add(node);
}
if(!stack.isEmpty()){
BSTNode<E> topNode=stack.get(stack.size()-1);
System.out.print(topNode.key+" ");
stack.remove(stack.size()-1);
if(topNode.rchild!=null){
node=topNode.rchild;
stack.add(node);
}
}
}
}
/**
* 測試
*/
public static void main(String[] args) {
BST<Integer> tree=new BST<Integer>();
tree.insert(new Integer(100));
tree.insert(new Integer(52));
tree.insert(new Integer(166));
tree.insert(new Integer(74));
tree.insert(new Integer(11));
tree.insert(new Integer(13));
tree.insert(new Integer(66));
tree.insert(new Integer(121));
tree.search(new Integer(11));
tree.InOrderTraverse();
tree.delete(new Integer(11));
tree.InOrderTraverse();
}
}