对于大量的输入数据,链表的线性访问时间太慢,不宜使用。树保证了大部分操作的运行时间平均为O(logN)。二叉查找树是两种库集合类TreeSet和TreeMap实现的基础,对于长的指令序列,基本上给出每种操作的O(logN)运行时间。
1.基础知识
一棵树是N个节点和N-1条边的集合,其中的一个节点叫做根。
树叶:没有儿子的节点;
兄弟:具有相同父亲的节点;
路径:路径的长是该路径上的边的条数;每个节点到它自己有长为0的路径;一棵树中从根到每个节点恰好存在一条路径。
节点深度:从根到节点的唯一路径的长;根的深度为0;一棵树的深度等于它最深树叶的深度,该深度总是等于这棵树的高。
节点高度:从节点到一片树叶的最长路径的长;树叶的高都是0;一棵树的高等于它的根的高。
1.1树的实现
1.2树的遍历及应用
先序遍历:
中序遍历:
后序遍历:
2.二叉树
一颗平均二叉树的深度要比节点个数N小的多,其平均深度为O(sqrt(N)).
对于二叉查找树,其深度平均值为O(logN).(极端情况可以达到N-1)
3.二叉查找树
性质:对于树中的每个节点X,它的左子树中所有项的值小于X中的项,右子树中所有项的值大于X中的项。
import java.util.Comparator;
/**
* 二叉查找树性质:
* 对于树中的每个节点t,他的左子树中所有项的值小于t中的项,而他的右子树中所有项的值大于t中的项。
* 平均情况分析:
* 假设所有的插入序列都是等可能的,则树的所有节点的平均深度为O(logN).
* 操作的平均运行时间O(logN).
* @author yulong
*
* @param <Type>
*/
public class BinarySearchTree<Type> {
private static class BinaryNode<Type>{
private Type element;
private BinaryNode<Type> left;
private BinaryNode<Type> right;
public BinaryNode(Type x){
this(x,null,null);
}
public BinaryNode(Type x,BinaryNode<Type> lft,BinaryNode<Type> rht){
element = x;
left = lft;
right = rht;
}
}
private BinaryNode<Type> root;//根节点
private int size;//节点个数
private Comparator<? super Type> cmp;
public BinarySearchTree(){
root = null;
size = 0;
}
public BinarySearchTree(Comparator<? super Type> c){
root = null;
size = 0;
cmp = c;
}
//自定义比较器方法
/*
*方式一:对Comparable型的对象进行比较
*int compareResult = x.compareTo(t.element);
*方式二:使用一个函数对象而不是要求这些项是Comparable的
*/
private int myCompare(Type x,Type y){
if(cmp!=null){
return cmp.compare(x, y);
}else{
return ((Comparable)x).compareTo(y);
}
}
public int size(){
return size;
}
public void makeEmpty(){
root = null;
size = 0;
}
public boolean isEmpty(){
return root==null;
}
public boolean contains(Type x){
return contains(x,root); //通过调用私有函数的方法,可以避免直接操作root产生的对原树的误操作
}
public Type findMin(){
return findMin(root).element;
}
public Type findMax(){
return findMax(root).element;
}
public void insert(Type x){
root = insert(x,root);
size++;
}
public void remove(Type x){
if(contains(x)){
root = remove(x,root);
size--;
}else{
//throw new java.util.NoSuchElementException();
}
}
public void printTree(){
printTree(root);
System.out.println();
}
private boolean contains(Type x,BinaryNode<Type> t){
if(t==null){
return false;
}
int compareResult = myCompare(x,t.element);
if(compareResult>0){
return contains(x,t.right);
}
else if(compareResult<0){
return contains(x,t.left);
}
else{
return true;
}
}
/**
* 递归,查找二叉查找树最小值
* @param t 根节点
* @return 含最小值的节点
*/
private BinaryNode<Type> findMin(BinaryNode<Type> t){
if(t==null){
return null;
}
else if(t.left==null){
return t;
}
else{
return findMin(t.left);
}
}
/**
* 非递归,查找二叉查找树最大值
* @param t 根节点
* @return 含最大值的节点
*/
private BinaryNode<Type> findMax(BinaryNode<Type> t){
while(t.right!=null){
t = t.right;
}
return t;
}
/**
* 插入节点
* @param x 待插入值
* @param t 根节点引用
* @return 根节点
*/
private BinaryNode<Type> insert(Type x,BinaryNode<Type> t){
if(t==null){
return new BinaryNode<Type>(x,null,null);
}
int compareResult = myCompare(x,t.element);
if(compareResult<0){
t.left = insert(x,t.left);
}
else if(compareResult>0){
t.right = insert(x,t.right);
}
else{
}
return t;
}
/**
* 删除节点:
* (1)删除叶子节点
* (2)删除含有一个儿子节点的子节点
* (3)删除含有两个儿子节点的子节点
* @param x 待删除元素
* @param t 根节点
* @return 根节点
*/
private BinaryNode<Type> remove(Type x,BinaryNode<Type> t){
if(t==null){
return t;
}
int compareResult = myCompare(x,t.element);
if(compareResult<0){
t.left = remove(x,t.left);
}
else if(compareResult>0){
t.right = remove(x,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.left!=null)?t.left:t.right;
}
return t;
}
/**
* 按从小到大顺序打印输出此二叉查找树(中序遍历)
* @param 树根节点
*/
private void printTree(BinaryNode<Type> t){
if(t==null){
System.out.println("空树");
}
if(t.left!=null && t.right!=null){
printTree(t.left);
System.out.print(t.element+" ");
printTree(t.right);
}
else if(t.left!=null && t.right==null){
printTree(t.left);
System.out.print(t.element+" ");
}
else if(t.right!=null){
System.out.print(t.element+" ");
printTree(t.right);
}
else{
System.out.print(t.element+" ");
}
}
}
4.AVL树—带有平衡条件的二叉查找树
性质:每个节点的左子树和右子树高度最多差1的二叉查找树(空树高度为-1)。
保证树的深度须是O(logN).
已知节点数N: logN<高度<1.44log(N+2)-1.328.
已知树高h : 最少节点数 s(h)=s(h-1)+s(h-2)+1.
/**
* AVL树(平衡二叉查找树)性质:
* 每个节点的左子树与右子树的高度最多差1(空树高为-1)
* AVL树的高度最多为1.44log(N+2)-1.328,但实际上只略大于logN。
* 已知节点数N: logN<树高<1.44log(N+2)-1.328;
* 已知树高h: 最少节点数S(h)=S(h-1)+S(h-2)+1.
* 插入节点导致a节点的不平衡(a点两棵子树高度差2),4种情况:
* (1)对a节点的左儿子的左子树进行插入,——>LL旋转
* (2)对a节点的左儿子的右子树进行插入,——>LR旋转
* (3)对a节点的右儿子的右子树进行插入,——>RR旋转
* (4)对a节点的右儿子的左子树进行插入。——>RL旋转
* @author yulong
*
* @param <Type>
*/
public class AVLTree<Type extends Comparable<? super Type>>{
private static class AVLNode<Type>{
public Type data;
public int height; //记录树的高度
public AVLNode<Type> left;
public AVLNode<Type> right;
public AVLNode(Type x){
this(x,null,null);
}
public AVLNode(Type x,AVLNode<Type> lt,AVLNode<Type> rt){
data = x;
left = lt;
right = rt;
height = 0;
}
}
private int size; //节点个数
private AVLNode<Type> root;
public AVLTree(){
clear();
}
/**
* 初始化、清空树
*/
public void clear(){
size = 0;
root = null;
}
/**
* 求树的节点个数
* @return 大小
*/
public int size(){
return size;
}
/**
* 求AVL树高
* @return 树高
*/
public int height(){
return height(root);
}
/**
* 判断树是否为空
* @return boolean
*/
public boolean isEmpty(){
return root==null;
}
/**
* 判断是否包含指定元素
* @param x 待判断元素
* @return boolean
*/
public boolean contains(Type x){
return contains(x,root);
}
/**
* 返回树中最小值
* @return 最小元素
*/
public Type findMin(){
return findMin(root);
}
/**
* 返回树中最大值
* @return 最大元素
*/
public Type findMax(){
return findMax(root);
}
/**
* 向AVL树中添加元素
* @param x 待添加元素
*/
public void insert(Type x){
root = insert(x,root);
size++;
}
/**
* 从AVL树中删除给定元素
* @param x 待删除元素
*/
/*public void remove(Type x){
if(contains(x)){
size--;
root = remove(x,root);
}else{
}
}*/
/**
* 按从小到大顺序打印输出AVL树中的元素(中序遍历)
*/
public void printTree(){
if(isEmpty()){
System.out.print("空树");
}else{
printTree(root);
}
System.out.println();
}
/**
* 求子树t的高度
* @param t 子树根节点
* @return 子树高度
*/
private int height(AVLNode<Type> t){
return t==null ? -1 : t.height;
}
/**
* 判断以结点t为根节点的子树是否包含指定的元素x
* @param x 待判断元素
* @param t 子树根节点
* @return boolean
*/
private boolean contains(Type x,AVLNode<Type> t){
if(t==null){
return false;
}
int compareResult = x.compareTo(t.data);
if(compareResult<0){
return contains(x,t.left);
}else if(compareResult>0){
return contains(x,t.right);
}else{
return true;
}
}
/**
* 非递归方式查找子树t中最小元素
* @param t 子树t根节点
* @return 最小元素
*/
private Type findMin(AVLNode<Type> t){
while(t.left!=null){
t = t.left;
}
return t.data;
}
/**
* 递归方式查找子树t中最大值
* @param t 子树t根节点
* @return 最大元素
*/
private Type findMax(AVLNode<Type> t){
if(t==null){
return null;
}else if(t.right==null){
return t.data;
}else{
return findMax(t.right);
}
}
/**
* 添加元素
* @param x 待添加元素
* @param t 等待添加元素的树
* @return 新的树根节点
*/
private AVLNode<Type> insert(Type x,AVLNode<Type> t){ //树根可能变换,返回新根节点
if(t==null){
return new AVLNode<Type>(x,null,null);
}
int compareResult = x.compareTo(t.data);
if(compareResult<0){
t.left = insert(x,t.left);
if(height(t.left)-height(t.right)==2){
if(x.compareTo(t.left.data)<0){
//LL型单旋转,把树高恢复到插入前水平
t = rotateWithLeftChild(t);
}else{
//LR型双旋转,把树高度恢复到插入前水平
t = doubleWithLeftChild(t);
}
}
}else if(compareResult>0){
t.right = insert(x,t.right);
if(height(t.right)-height(t.left)==2){
if(x.compareTo(t.right.data)>0){
//RR型单旋转,把树高恢复到插入前水平
t = rotateWithRightChild(t);
}else{
//RL型双旋转,把树高度恢复到插入前水平
t = doubleWithRightChild(t);
}
}
}else{
}
/*
* 以上的旋转操作只是在旋转方法内部改变了树的高度,并没有实际改变返回节点在外部的高度。
*/
//!!更新树高(起初忘记这一步,导致不能生成AVL树)
t.height = Math.max(height(t.left), height(t.right))+1;
return t; //返回AVL树新的根节点
}
private AVLNode<Type> rotateWithLeftChild(AVLNode<Type> t){
AVLNode<Type> t1 = t.left;
t.left = t1.right;
t1.right = t;
t.height = Math.max(height(t.left), height(t.right))+1;
t1.height = Math.max(height(t1.left),t.height)+1;
return t1;
}
private AVLNode<Type> rotateWithRightChild(AVLNode<Type> t){
AVLNode<Type> t1 = t.right;
t.right = t1.left;
t1.left = t;
t.height = Math.max(height(t.left), height(t.right))+1;
t1.height = Math.max(height(t1.right),t.height)+1;
return t1;
}
private AVLNode<Type> doubleWithLeftChild(AVLNode<Type> t){
t.left = rotateWithRightChild(t.left);
return rotateWithLeftChild(t);
}
private AVLNode<Type> doubleWithRightChild(AVLNode<Type> t){
t.right = rotateWithLeftChild(t.right);
return rotateWithRightChild(t);
}
/**
* 删除元素
* @param x
* @param t
* @return
*/
private AVLNode<Type> remove(Type x,AVLNode<Type> t){
...
}
/**
* 从小到大输出AVL树中元素(中序遍历)
* @param t 待输出子树的根节点
*/
private void printTree(AVLNode<Type> t){
if(t!=null){
printTree(t.left);
System.out.print(t.data+" ");
printTree(t.right);
}
}
}
5.伸展树
摊还运行时间:当M次操作的序列总的最坏情形运行时间为O(Mf(N))时,它的摊还运行时间为O(f(N)).
一棵伸展树每次操作的摊还代价为O(logN).
性质:保证从空树开始连续M次对树的操作最多花费O(MlogN)时间。
当一个节点被访问后,它就要经过一系列的AVL树的旋转被推到根上。
伸展树不要求保留高度或平衡信息。
5.1展开
两种旋转情况:
展开操作不仅将访问的节点移动到根处,而且还把访问路径上的大部分节点的深度大致减少一半。
删除:可通过访问要被删除的节点来执行删除操作。此操作将节点上推到根处,如果删除该节点,则得到两棵子树TL和TR,找到TL中的最大元素,该元素被移到TL的根,此时,使TR变为其右儿子,从而完成删除。
6.树的遍历
对于二叉查找树,其中序遍历与后续遍历总的运行时间都为O(N).
7.B树
原则上B树保证只有少数的磁盘访问。
阶为M的B树有下列特性:
1)数据项存储在树叶上
2)非叶节点存储直到M-1个关键字来指示搜索方向;关键字i代表子树i+1中的最小关键字
3)树的根或者是一片树叶,或者其儿子数在2和M之间
4)除根外,所有非树叶节点的儿子数在[M/2]和M之间
5)所有的树叶都在相同的深度上,并有[L/2]~L个数据项。
B树增加高度的唯一方式:当分裂树根时,得到两个树根,此时建立一个新的根,这个根以分裂得到的两个树根作为它的两个儿子(这是准许树根可以至少有两个儿子的原因)。
B树降低高度的唯一方式:若被删元所在树叶的数据项已经是最小值,(1)可以在临界点没有达到最小值时领养一个临项,(2)如果临界点已经达到最小值,可以与该临界点联合形成一片满叶,这意味着父节点失去一个儿子,这个过程可以上行到根,如果领养过程使得根只剩下一个儿子,那么删除该根,让它的儿子作为树的新根。
8.标准库中的集合与映射
8.1set接口
8.2Map接口
8.3TreeSet类和TreeMap类的实现
Java要求其支持的基本add,remove和contains操作以对数最坏情形时间完成。因此基本实现方法是平衡二叉查找树,但一般并不使用AVL树,而经常使用自顶向下的红黑树。