二叉查找樹(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();
}
}