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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值