二叉树的性质及二叉查找树的实现

二叉树的性质


  1. 树中的结点数等于数的边数加1,也等于所有的度数之和加1(此性质适合所有的树)
  2. 在二叉树的第i层上最多有 2 i 2^i 2i个结点,其中i从第0层开始
  3. 高度为h的二叉树至多有 2 h + 1 − 1 2^{h+1}-1 2h+11个结点
  4. 对于任意一棵二叉树T,如果其终端节点数为n0,度为2的结点数为n2,则n0 = n2 + 1
    (可以使用性质1来推导,n = n0 + n2 + n1 , n = n2 × 2 + n1 + 1, 两式结合得到n0 = n2 + 1)
  5. 有n个结点的完全二叉树的高度为 l o g n log ^ n logn
  6. 含有n ≥ \geq 1个结点的二叉树的高度至多为n-1;高度至少为 l o g n log^n logn

二叉查找树

对于任意的二叉查找树,总有左子树的元素小于根结点的元素,根结点的元素小于右子树的元素。从上面的性质中我们可以推导出二叉查找树的最小元素树的最左侧,且其没有左孩子;最大元素在数的最右侧,其没有右孩子。二叉查找树是两种库集合类TreeSet和TreeMap实现的基础,所以了解二叉查找树对我们有很大的帮助。另外,二叉查找树可以训练我们的递归思想,因为里面很多操作都用到了递归,比如contains、insert、remove等等。

以下是二叉查找树的实现(实现参考了《数据结构与算法分析(Java语言描述)》),需要说明以下两点:

  • 类中包含了一个成员内部类BinaryNode作为二叉查找树的结点
  • 需要说明的是在进行remove操作时需要考虑三种情况,删除的结点是否有孩子,如果有,一个还是两个

/*
 * 二叉查找树的性质:
 * 		1. 左子树比根结点要小,最小的元素在最左边
 * 		2. 右子树要比根结点大,最大的元素在最右边
 */
public class BinarySearchTree<T extends Comparable<? super T>> {

	private BinaryNode<T> root; // 根结点
	private Comparator<? super T> cmp; // 自定义排序

	public BinarySearchTree() {
		this(null);
	}

	public BinarySearchTree(Comparator<? super T> cmp) {
		root = null;
		this.cmp = cmp;
	}

	public void makeEmpty() { // 将树置空
		root = null;
	}

	public boolean isEmpty() { // 判断树是否为空
		return root == null;
	}

	public boolean contains(T ele) { // 是否包含指定元素
		return contains(ele, root);
	}

	private boolean contains(T ele, BinaryNode<T> t) {

		if (t == null)
			return false; // 基线条件 如果为空则返回false

		int compareResult = myCompare(ele, t.element);

		if (compareResult < 0) { // 小于0说明该元素比根结点小,在左子树
			return contains(ele, t.left);
		} else if (compareResult > 0) { // 大于0说明该元素比根结点大,在右子树
			return contains(ele, t.right);
		} else { // 如果等于0 则说明匹配到了
			return true;
		}
		
	}

	public T findMin() throws UnderflowException { // 查询最小的元素
		if (isEmpty())
			throw new UnderflowException("树空!");
		return findMin(root).element;
	}

	// 查询最小的结点,在最左边
	private BinaryNode<T> findMin(BinaryNode<T> t) {
		
		if (t == null)
			return null; // t 为null说明为空树
		else if (t.left == null)
			return t; // 如果t的左子树为null 则说明为最左边的元素
		
		return findMin(t.left); // 递归调用
	}

	public T findMax() throws UnderflowException {
		if (isEmpty())
			throw new UnderflowException("树空!");
		
		return findMax(root).element;
	}

	// 查询最大的结点,在最右边
	private BinaryNode<T> findMax(BinaryNode<T> t) {
		
		if(t == null) return null;	// 类似于findMin
		else if(t.right == null ) return t;
		
		return findMax(t.right);
	}

	public void insert(T ele) { // 向树中插入指定的元素
		root = insert(ele, root);
	}
	/*
	 * 插入类似于contains,先找到需要插入的位置
	 * 如果t 为空,则说明这个位置就是插入的位置,
	 * 		构造一个新节点,左右子树为null,数据域为ele
	 * 然后返回这棵树,刷新引用
	 */
	private BinaryNode<T> insert(T ele, BinaryNode<T> t) {
		
		if(t == null) return new BinaryNode<T>(ele,null,null);
		
		int compareResult = myCompare(ele, t.element);
		
		if(compareResult < 0) t.left = insert(ele,t.left);
		else if(compareResult > 0) t.right = insert(ele,t.right);
		else ;	// 元素重复,不做处理
		
		return t;	// 返回新的树
	}

	public void remove(T ele) { // 删除树中指定元素
		root = remove(ele, root);
	}
	/*
	 * 删除操作
	 * 		考虑三种情况
	 * 			1. 删除的结点如果没有孩子,即该结点为叶子结点,可以直接删除	
	 * 			2. 删除的结点如果只有一个孩子,则可以将这个孩子替换需要删除的结点
	 * 			3. 删除的结点如果有两个孩子,则需要先找到右子树的最小结点,将
	 * 			         该结点的数据域替换需要删除的结点的数据域,然后再递归删除该最小结点
	 * 
	 * 			这个递归最终替换成删除只有一个孩子或没有孩子的结点!
	 * 
	 * 		步骤:
	 * 			找到需要删除的位置
	 * 			判断是否有两个孩子
	 * 				两个孩子则获取右子树最小结点的数据域并赋值
	 * 				递归删除最小结点
	 * 			如果没有两个孩子则说明只有一个或没有孩子
	 * 				判断有哪个孩子,将父节点指向这个孩子,没有则为null
	 */
	private BinaryNode<T> remove(T ele, BinaryNode<T> t) {
		
		if( t == null) return t;	// 说明不存在ele,返回t
		
		int compareResult = ele.compareTo(t.element);
		
		if(compareResult < 0) t.left = remove(ele,t.left);
		else if(compareResult > 0) t.right = remove(ele,t.right);
		else if(t.left != null && t.right !=null) {	// 如果有两个孩子
			t.element = findMin(t.right).element;	
			t.right = remove(t.element,t.right);	// 递归调用
		}
		else 	// 这一步处理了没有孩子以及只有一个孩子,如果有一个孩子,则把这个孩子覆盖t即可,否则将t置为null
			t = (t.left != null)?t.left:t.right;	// 基线条件
		
		return t;
	}
	
	public void printTree() {
		if(isEmpty()) System.out.println("树为空!");
		else printTree(root);
	}
	
	private void printTree(BinaryNode<T> t) { // 打印树结构,中序遍历
		
		if(t != null) {
			printTree(t.left);
			System.out.println(t.element);
			printTree(t.right);
		}
		
	}

	/*
	 * 如果存在自定义排序,则使用自定义排序 不存在则使用自让排序,T必须实现Comparable接口
	 */
	private int myCompare(T lhs, T rhs) {
		if (cmp != null) {
			return cmp.compare(lhs, rhs);
		} else
			return ((Comparable) lhs).compareTo(rhs);	// 强转保证一定是Comparable类型
	}

	/*
	 * 二叉查找树的结点 数据域: element 左子树: left 右子树: right
	 */
	private static class BinaryNode<T> {

		T element;
		BinaryNode<T> left;
		BinaryNode<T> right;

		BinaryNode(T theElement) {
			this(theElement, null, null);
		}

		BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt) {
			element = theElement;
			left = lt;
			right = rt;
		}
	}

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值