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