二叉查找树与中间值查找

二叉查找树是具有如下性质的一种二叉树:对于任一结点x,x的左子树结点的关键字均不大于x,右子树结点的关键字均不小于x。

二叉查找树的特点是位置决定了顺序,所以在不对关键字进行排序的情况下,通过位置关系就能找到特定大小的关键字结点。例如,对二叉查找树进行中根遍历就能按升序输出所有的关键字。

 

二叉查找树的搜索:从根节点开始,对于树中任一结点k,若k的关键字大于待搜索的值x,则搜索k的左子树;若小于,则搜索k的右子树;若等于,则找到。

最大关键字和最小关键字:最大关键字是二叉查找树的最右叶结点,最小关键字是最左叶结点。

 

前趋和后继:某结点的前趋是指关键字比该结点小的所有结点中最大的一个,而后继则是关键字比该结点大的所有结点中最小的一个。就后继而言:

1.      若结点x的右子树非空,则x的后继是x的右子树的最左叶结点;

2.      若结点x的右子树为空,则x的后继是x的最低祖先结点,且该后继结点的左儿子也必须是x的祖先。

前趋则跟后继正好对称。

 

插入:与二叉查找树的搜索相类似,小于当前结点的值则向左,大于当前结点的值则向右,最终将待插入的值作为满足当前二叉查找树性质的某个叶结点。

 

删除:对于待删结点z:

1.      若z没有左右子结点(即z为叶结点),直接删除z;

2.      若z只有一个子结点,删除z,并将z的子结点接到z的父结点的对应位置;

3.      若z左右子结点都有,则找到z的后继y,删除y(以1或2中的方式,因为后继必然至多有一个子结点),再将y的数据拷贝到z中。

 

二叉查找树的好处是它的上述7种操作(搜索、最大、最小、前趋、后继、插入、删除)的时间复杂度都是O(h),其中h是二叉树的树高。因此它的最差时间复杂度也才只有O(n),平均和最佳时间复杂度有只有O(lgn)。

 

如下是二叉查找树的一种Java实现:

package cn.liujian;

public class BinaryTreeNode<E extends Comparable<E>> {
	private E value;
	private BinaryTreeNode<E> left;
	private BinaryTreeNode<E> right;
	private BinaryTreeNode<E> parent;
	
	public BinaryTreeNode(E e) {
		if (e == null) {
			throw new IllegalArgumentException();
		}
		
		value = e;
		left = null;
		right = null;
		parent = null;
	}
	
	public E getValue() {
		return value;
	}
	public void setValue(E value) {
		if (value == null) {
			throw new IllegalArgumentException();
		}
		
		this.value = value;
	}
	public BinaryTreeNode<E> getLeft() {
		return left;
	}
	public void setLeft(BinaryTreeNode<E> left) {
		this.left = left;
	}
	public BinaryTreeNode<E> getRight() {
		return right;
	}
	public void setRight(BinaryTreeNode<E> right) {
		this.right = right;
	}
	public BinaryTreeNode<E> getParent() {
		return parent;
	}
	public void setParent(BinaryTreeNode<E> parent) {
		this.parent = parent;
	}

	// 为以本结点为根的子树插入新结点
	public void addChild(BinaryTreeNode<E> child) {
		if (child.getValue() == null) {
			throw new IllegalArgumentException();
		}
		
		if (child.getValue().compareTo(value) <= 0) {
			if (left == null) {
				left = child;
				child.setParent(this);
			} else {
				left.addChild(child);
			}
		} else {
			if (right == null) {
				right = child;
				child.setParent(this);
			} else {
				right.addChild(child);
			}
		}
	}
	
	// 二叉树搜索
	public BinaryTreeNode<E> searchNode(E value) {
		if (value == null) {
			return null;
		}
		
		// 递归实现
		if (this.value.compareTo(value) == 0) {
			return this;
		} else if (this.value.compareTo(value) > 0) {
			return (left != null) ? left.searchNode(value) : null;
		} else {
			return (right != null) ? right.searchNode(value) : null;
		}
		
//		// 迭代实现
//		BinaryTreeNode<E> target = this;
//		while (target != null && target.getValue().compareTo(value) != 0) {
//			if (target.getValue().compareTo(value) > 0) {
//				target = target.getLeft();
//			} else {
//				target = target.getRight();
//			}
//		}
//		return target;
	}
	
	// 从以本结点为根的子树中删除结点
	public BinaryTreeNode<E> deleteNode(E value, BinaryTreeNode<E> root) {
		BinaryTreeNode<E> node = searchNode(value);
		
		BinaryTreeNode<E> target = node;
		if (node != null) {
			if (node.getLeft() != null && node.getRight() != null) {
				target = node.successor();
			}
			
			BinaryTreeNode<E> child = (target.getLeft() != null) ? target.getLeft() : target.getRight();
			if (child != null) {
				child.setParent(target.getParent());
			}
			
			if (target.getParent() == null) {
				root = child;
			} else {
				if (target.getParent().getLeft() == target) {
					target.getParent().setLeft(child);
				} else {
					target.getParent().setRight(child);
				}
			}
			
			if (target != node) {
				node.setValue(target.getValue());
			}
		}
		
		
		return target;
	}
	
	// 返回本子树中的最小结点
	public BinaryTreeNode<E> getMinNode() {
		BinaryTreeNode<E> target = this;
		while (target.getLeft() != null) {
			target = target.getLeft();
		}
		return target;
	}
	
	// 返回本子树中的最大结点
	public BinaryTreeNode<E> getMaxNode() {
		BinaryTreeNode<E> target = this;
		while (target.getRight() != null) {
			target = target.getRight();
		}
		return target;
	}
	
	// 返回本结点的后继结点
	public BinaryTreeNode<E> successor() {
		if (right != null) {
			return right.getMinNode();
		}
		
		BinaryTreeNode<E> target = parent;
		BinaryTreeNode<E> child = this;
		while (target != null && child == target.getRight()) {
			child = target;
			target = target.getParent();
		}
		return target;
	}
	
	// 返回本结点的前趋结点
	public BinaryTreeNode<E> predecessor() {
		if (left != null) {
			return left.getMaxNode();
		}
		
		BinaryTreeNode<E> target = parent;
		BinaryTreeNode<E> child = this;
		while (target != null && child == target.getLeft()) {
			child = target;
			target = target.getParent();
		}
		return target;
	}
}

基于这样一棵二叉查找树,可以很容易根据位置关系找到一组数据的顺序中间值(第m = size / 2 + 1小的值)。当然,需要对上述代码进行修改,以得到二叉查找树的大小size、根结点左子树结点数a和根结点右子树结点数b。思路是:

1.      若a = m – 1,则根结点就是中间值;

2.      若a > m – 1,则中间值在根结点的左子树中,且是根结点的第a – m + 1个前趋;

3.      若a < m – 1,则中间值在根结点的右子树中,且是根结点的第b + m – size个后继。

 

可见,如果用二叉查找树来查找中间值,其运行时间始终是线性的,而且如果树平衡度高的话,运行时间也更少。

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/sadfishsc/article/details/8033120
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞