動態查詢---->二叉查詢樹(Binary Search Tree)
二叉查詢樹(Binary Search Tree)
一、二叉查詢樹的定義
----或是一棵空樹;或者是具有如下性質的非空二叉樹:
(1)左子樹的所有結點均小於根的值;
(2)右子樹的所有結點均大於根的值;
結論:中序遍歷一棵二叉查詢樹可以得到一個按關鍵字遞增的有序序列。
1、查詢
查詢的遞迴實現 :
private Node binTSearchRe(BinTreeNode rt, Object ele) { if (rt == null) return null; switch (strategy.compare(ele, rt.getData())) { case 0: return rt; // 等於 case -1: return binTSearchRe(rt.getLChild(), ele);// 小於 default: return binTSearchRe(rt.getRChild(), ele);// 大於 } }
呼叫
// 返回查詢表中與元素ele關鍵字相同的元素位置;否則,返回null
public Node search(Object ele) {
return binTSearch(root, ele);
}
查詢的非遞迴實現
private Node binTSearch(BinTreeNode rt, Object ele) { while (rt != null) { switch (strategy.compare(ele, rt.getData())) { case 0: return rt; // 等於 case -1: rt = rt.getLChild(); continue;// 小於 default: rt = rt.getRChild(); // 大於 } } return null; }
查詢分析
含有n個結點的二叉查詢樹的平均查詢長度和樹的形態有關。
在具有n個結點的二叉樹中,樹的最小高度為log n ,即在最好的情況下二叉查詢樹的平均查詢長度與折半查詢一樣與log n 成正比。具有n個結點的二叉樹可以退化為一個單鏈表,其深度為n-1,此時其平均查詢長度為(n+1)/2,與順序查詢相同,這是最差的情況。在平均情況下,如果隨機生成二叉查詢樹,其平均查詢長度和log n 是等數量級的。
2、最大最小值
在二叉查詢樹中,最小元素總是能夠通過根結點向左不斷深入,直到到達最左的一個葉子結點找到;而最大元素總是能夠通過根結點向右不斷深入,直到到達最右的一個葉子結點找到。
Max
public Node max(BinTreeNode v) { if (v != null) while (v.hasRChild()) v = v.getRChild(); return v; }
3、前驅和後續
在二叉查詢樹中確定某個結點v 的後續結點的演算法思想如下:如果結點v 有右子樹,那麼v 的後續結點是v 的右子樹中關鍵字最小的;如果結點v 右子樹為空,並且v 的後續結點存在,那麼v 的後續結點是從v(包含v)到根的路徑上第一個作為左孩子結點的父結點。
程式碼:求v在中序遍歷序列中的後續結點
// 返回結點v在中序遍歷序列中的後續結點
private BinTreeNode getSuccessor(BinTreeNode v) {
if (v == null)
return null;
if (v.hasRChild())
return (BinTreeNode) min(v.getRChild());
while (v.isRChild())
v = v.getParent();
return v.getParent();
}
程式碼:求v在中序遍歷序列中的前驅結點
// 返回結點v在中序遍歷序列中的前驅結點
private BinTreeNode getPredecessor(BinTreeNode v) {
if (v == null)
return null;
if (v.hasLChild())
return (BinTreeNode) max(v.getLChild());
while (v.isLChild())
v = v.getParent();
return v.getParent();
}
4、插入
為了判定新結點的插入位置,需要從根結點開始逐層深入,判斷新結點關鍵字與各子樹根結點關鍵字的大小,若新結點關鍵字小,則向相應根結點的左子樹深入,否則向右子樹深入;直到向某個結點的左子樹深入而其左子樹為空,或向某個結點的右子樹深入而其右子樹為空時,則確定了新結點的插入位置。
// 按關鍵字插入元素ele
public void insert(Object ele) {
BinTreeNode p = null;
BinTreeNode current = root;
while (current != null) { // 找到待插入位置
p = current;
if (strategy.compare(ele, current.getData()) < 0)
current = current.getLChild();
else
current = current.getRChild();
}
if (p == null)
root = new BinTreeNode(ele); // 樹為空
else if (strategy.compare(ele, p.getData()) < 0)
p.setLChild(new BinTreeNode(ele));
else
p.setRChild(new BinTreeNode(ele));
}
5、刪除演算法
對於二叉查詢樹,刪除樹上一個結點相當於刪除有序序列中的一個記錄,刪除後仍需保持二叉查詢樹的特性。在二叉查詢樹中刪除的結點不總是葉子結點,因此在刪除一個非葉子結點時需要處理該結點的子樹。
如何刪除一個結點?
下面我們分三種情況討論結點v 的刪除:
⑴如果結點v為葉子結點,即其左右子樹Pl和Pr均為空,此時可以直接刪除該葉子結點v,而不會破壞二叉查詢樹的特性,因此直接從樹中摘除v即可。
⑵如果結點v只有左子樹Pl或只有右子樹Pr,此時,當結點v是左孩子時,只要令Pl或Pr為其雙親結點的左子樹即可;當結點v是右孩子時,只要令Pl或Pr為其雙親結點的右子樹即可。
⑶如果結點v既有左子樹Pl又有右子樹Pr,此時,不可能進行如上簡單處理。為了在刪除結點v之後,仍然保持二叉查詢樹的特性,我們必須保證刪除v之後,樹的中序序列必須仍然有序。為此,我們可以先用中序序列中結點v的前驅或後序替換v,然後刪除其前驅或後序結點即可,此時v的前驅或後序結點必然是沒有右孩子或沒有左孩子的結點,其刪除操作可以使用前面規定的方法完成。
情況一舉例:由於結點2 是葉子結點,則直接摘除即可。
情況2舉例:例如在圖所示的二叉查詢樹中刪除結點3 和7,由於3 是左孩子,因此在刪除結點3 之後,將其右子樹設為其父結點6 的左子樹即可;同樣,因為7 是右孩子,因此在刪除結點7之後,將其右子樹設為其父結點6 的右子樹即可。
情況3舉例:例如在圖所示的樹中刪除結點15,由於結點15既有左子樹又有右子樹,則此時,可以先找到其前驅結點13,並用13替換15,然後刪除結點13即可。
public Object remove(Object ele) {
BinTreeNode v = (BinTreeNode) binTSearch(root, ele);
if (v == null)
return null; // 查詢失敗
BinTreeNode del = null; // 待刪結點
BinTreeNode subT = null; // 待刪結點的子樹
if (!v.hasLChild() || !v.hasRChild()) // 確定待刪結點
del = v;
else {
del = getPredecessor(v);
Object old = v.getData();
v.setData(del.getData());
del.setData(old);
}
// 此時待刪結點只有左子樹或右子樹
if (del.hasLChild())
subT = del.getLChild();
else
subT = del.getRChild();
if (del == root) { // 若待刪結點為根
if (subT != null)
subT.sever(); //斷開與父親的關係
root = subT;
} else if (subT != null) {
// del為非葉子結點
if (del.isLChild())
del.getParent().setLChild(subT);
else
del.getParent().setRChild(subT);
} else
// del為葉子結點
del.sever();
return del.getData();
}
將資料元素構造成二叉查詢樹的優點:
①查詢過程與順序結構有序表中的折半查詢相似,查詢效率高;
②中序遍歷此二叉樹,將會得到一個關鍵字的有序序列(即實現了排序運算);
③如果查詢不成功,能夠方便地將被查元素插入到二叉樹的葉子結點上,而且插入或刪除時只需修改指標而不需移動元素。
總結:二叉查詢樹既有類似於折半查詢的特性,又採用了連結串列儲存,它是動態查詢表的一種適宜表示。若資料元素的輸入順序不同,則得到的二叉查詢樹形態也不同。
附:全部程式碼
package dsa.adt;
import dsa.adt.BinaryTreeLinked;
import dsa.adt.SearchTable;
import dsa.strategy.Strategy;
import dsa.strategy.DefaultStrategy;
public class BSTree extends BinaryTreeLinked implements SearchTable {
protected BinTreeNode startBN; // 在AVL樹中重新平衡的起始結點
// 構造方法
public BSTree() {
this(new DefaultStrategy());
}
public BSTree(Strategy strategy) {
this.root = null;
this.strategy = strategy;
startBN = null;
}
// 查詢查詢表當前的規模
public int getSize() {
return root == null ? 0 : root.getSize();
}
// 判斷查詢表是否為空
public boolean isEmpty() {
return getSize() == 0;
}
// 返回查詢表中與元素ele關鍵字相同的元素位置;否則,返回null
public Node search(Object ele) {
return binTSearch(root, ele);
}
private Node binTSearchRe(BinTreeNode rt, Object ele) {
if (rt == null)
return null;
switch (strategy.compare(ele, rt.getData())) {
case 0:
return rt; // 等於
case -1:
return binTSearchRe(rt.getLChild(), ele);// 小於
default:
return binTSearchRe(rt.getRChild(), ele);// 大於
}
}
private Node binTSearch(BinTreeNode rt, Object ele) {
while (rt != null) {
switch (strategy.compare(ele, rt.getData())) {
case 0:
return rt; // 等於
case -1:
rt = rt.getLChild();
break;// 小於
default:
rt = rt.getRChild(); // 大於
}
}
return null;
}
// 返回所有關鍵字與元素ele相同的元素位置
public Iterator searchAll(Object ele) {
LinkedList list = new LinkedListDLNode();
binTSearchAll(root, ele, list);
return list.elements();
}
public void binTSearchAll(BinTreeNode rt, Object ele, LinkedList list) {
if (rt == null)
return;
int comp = strategy.compare(ele, rt.getData());
if (comp <= 0)
binTSearchAll(rt.getLChild(), ele, list);
if (comp == 0)
list.insertLast(rt);
if (comp >= 0)
binTSearchAll(rt.getRChild(), ele, list);
}
// 按關鍵字插入元素ele
public void insert(Object ele) {
BinTreeNode p = null;
BinTreeNode current = root;
while (current != null) { // 找到待插入位置
p = current;
if (strategy.compare(ele, current.getData()) < 0)
current = current.getLChild();
else
current = current.getRChild();
}
startBN = p; // 待平衡出發點
if (p == null)
root = new BinTreeNode(ele); // 樹為空
else if (strategy.compare(ele, p.getData()) < 0)
p.setLChild(new BinTreeNode(ele));
else
p.setRChild(new BinTreeNode(ele));
}
// 若查詢表中存在與元素ele關鍵字相同元素,則刪除一個並返回;否則,返回null
public Object remove(Object ele) {
BinTreeNode v = (BinTreeNode) binTSearch(root, ele);
if (v == null)
return null; // 查詢失敗
BinTreeNode del = null; // 待刪結點
BinTreeNode subT = null; // del的子樹
if (!v.hasLChild() || !v.hasRChild()) // 確定待刪結點
del = v;
else {
del = getPredecessor(v);
Object old = v.getData();
v.setData(del.getData());
del.setData(old);
}
startBN = del.getParent(); // 待平衡出發點
// 此時待刪結點只有左子樹或右子樹
if (del.hasLChild())
subT = del.getLChild();
else
subT = del.getRChild();
if (del == root) { // 若待刪結點為根
if (subT != null)
subT.sever();
root = subT;
} else if (subT != null) {
// del為非葉子結點
if (del.isLChild())
del.getParent().setLChild(subT);
else
del.getParent().setRChild(subT);
} else
// del為葉子結點
del.sever();
return del.getData();
}
// 返回以v為根的二叉查詢樹中最小(大)元素的位置
public Node min(BinTreeNode v) {
if (v != null)
while (v.hasLChild())
v = v.getLChild();
return v;
}
public Node max(BinTreeNode v) {
if (v != null)
while (v.hasRChild())
v = v.getRChild();
return v;
}
// 返回結點v在中序遍歷序列中的前驅結點
private BinTreeNode getPredecessor(BinTreeNode v) {
if (v == null)
return null;
if (v.hasLChild())
return (BinTreeNode) max(v.getLChild());
while (v.isLChild())
v = v.getParent();
return v.getParent();
}
// 返回結點v在中序遍歷序列中的後續結點
private BinTreeNode getSuccessor(BinTreeNode v) {
if (v == null)
return null;
if (v.hasRChild())
return (BinTreeNode) min(v.getRChild());
while (v.isRChild())
v = v.getParent();
return v.getParent();
}
}