1. 程式人生 > >動態查詢---->二叉查詢樹(Binary Search Tree)

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