1. 程式人生 > >淺談演算法和資料結構(7):二叉查詢樹

淺談演算法和資料結構(7):二叉查詢樹

前文介紹了符號表的兩種實現,無序連結串列和有序陣列,無序連結串列在插入的時候具有較高的靈活性,而有序陣列在查詢時具有較高的效率,本文介紹的二叉查詢樹(Binary Search Tree,BST)這一資料結構綜合了以上兩種資料結構的優點。

二叉查詢樹具有很高的靈活性,對其優化可以生成平衡二叉樹,紅黑樹等高效的查詢和插入資料結構,後文會一一介紹。

一 定義

二叉查詢樹(Binary Search Tree),也稱有序二叉樹(ordered binary tree),排序二叉樹(sorted binary tree),是指一棵空樹或者具有下列性質的二叉樹:

1. 若任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;

2. 若任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;

3. 任意節點的左、右子樹也分別為二叉查詢樹。

4. 沒有鍵值相等的節點(no duplicate nodes)。

如下圖,這個是普通的二叉樹:

在此基礎上,加上節點之間的大小關係,就是二叉查詢樹:

二 實現

在實現中,我們需要定義一個內部類Node,它包含兩個分別指向左右節點的Node,一個用於排序的Key,以及該節點包含的值Value,還有一個記錄該節點及所有子節點個數的值Number。

Java
1234567891011121314151617181920 publicclassBinarySearchTreeSymbolTable<TKey,TValue>:SymbolTables<TKey,TValue>where TKey:IComparable<TKey>,IEquatable<TValue>{privateNode root;privateclassNode{publicNodeLeft{get;set;}publicNodeRight{get
;set;}publicintNumber{get;set;}publicTKeyKey{get;set;}publicTValueValue{get;set;}publicNode(TKey key,TValue value,intnumber){this.Key=key;this.Value=value;this.Number=number;}}...}

查詢

查詢操作和二分查詢類似,將key和節點的key比較,如果小於,那麼就在Left Node節點查詢,如果大於,則在Right Node節點查詢,如果相等,直接返回Value。

該方法實現有迭代和遞迴兩種。

遞迴的方式實現如下:

Java
1234567891011121314151617181920212223 publicoverride TValue Get(TKey key){TValue result=default(TValue);Node node=root;while(node!=null){if(key.CompareTo(node.Key)>0){node=node.Right;}elseif(key.CompareTo(node.Key)<0){node=node.Left;}else{result=node.Value;break;}}returnresult;}

迭代的如下:

Java
12345678910111213 publicTValue Get(TKey key){returnGetValue(root,key);}privateTValue GetValue(Node root,TKey key){if(root==null)returndefault(TValue);intcmp=key.CompareTo(root.Key);if(cmp>0)returnGetValue(root.Right,key);elseif(cmp<0)returnGetValue(root.Left,key);elsereturnroot.Value;}

插入

插入和查詢類似,首先查詢有沒有和key相同的,如果有,更新;如果沒有找到,那麼建立新的節點。並更新每個節點的Number值,程式碼實現如下:

Java
123456789101112131415161718192021222324 publicoverride voidPut(TKey key,TValue value){root=Put(root,key,value);}privateNode Put(Nodex,TKey key,TValue value){//如果節點為空,則建立新的節點,並返回//否則比較根據大小判斷是左節點還是右節點,然後繼續查詢左子樹還是右子樹//同時更新節點的Number的值if(x==null)returnnewNode(key,value,1);intcmp=key.CompareTo(x.Key);if(cmp<0)x.Left=Put(x.Left,key,value);elseif(cmp>0)x.Right=Put(x.Right,key,value);elsex.Value=value;x.Number=Size(x.Left)+Size(x.Right)+1;returnx;}privateintSize(Node node){if(node==null)return0;elsereturnnode.Number;}

插入操作圖示如下:

下面是插入動畫效果:

隨機插入形成樹的動畫如下,可以看到,插入的時候樹還是能夠保持近似平衡狀態:

最大最小值

如下圖可以看出,二叉查詢樹的最大最小值是有規律的:

從圖中可以看出,二叉查詢樹中,最左和最右節點即為最小值和最大值,所以我們只需迭代呼叫即可。

Java
1234567891011121314151617181920212223 publicoverride TKey GetMax(){TKey maxItem=default(TKey);Nodes=root;while(s.Right!=null){s=s.Right;}maxItem=s.Key;returnmaxItem;}publicoverride TKey GetMin(){TKey minItem=default(TKey);Nodes=root;while(s.Left!=null){s=s.Left;}minItem=s.Key;returnminItem;}

以下是遞迴的版本:

Java
123456789101112131415161718192021 publicTKey GetMaxRecursive(){returnGetMaxRecursive(root);}privateTKey GetMaxRecursive(Node root){if(root.Right==null)returnroot.Key;returnGetMaxRecursive(root.Right);}publicTKey GetMinRecursive(){returnGetMinRecursive(root);}privateTKey GetMinRecursive(Node root){if(root.Left==null)returnroot.Key;returnGetMinRecursive(root.Left);}

Floor和Ceiling

查詢Floor(key)的值就是所有<=key的最大值,相反查詢Ceiling的值就是所有>=key的最小值,下圖是Floor函式的查詢示意圖:

以查詢Floor為例,我們首先將key和root元素比較,如果key比root的key小,則floor值一定在左子樹上;如果比root的key大,則有可能在右子樹上,當且僅當其右子樹有一個節點的key值要小於等於該key;如果和root的key相等,則floor值就是key。根據以上分析,Floor方法的程式碼如下,Ceiling方法的程式碼類似,只需要把符號換一下即可:

Java
1234567891011121314151617181920 publicTKey Floor(TKey key){Nodex=Floor(root,key);if(x!=null)returnx.Key;elsereturndefault(TKey);}privateNode Floor(Nodex,TKey key){if(x==null)returnnull;intcmp=key.CompareTo(x.Key);if(cmp==0)returnx;if(cmp<0)returnFloor(x.Left,key);else{Node right=Floor(x.Right,key);if(right