1.01 树结构
树结构本身是一种天然的组织结构,将数据使用树结构存储后,会大幅高效率。
树是包含n(n>=0)个结点的有穷集,其中:
(1)每个元素称为结点(node);
(2)有一个特定的结点被称为根结点或树根(root)。
(3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个集合
Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。
空集合也是树,称为空树。空树中没有结点。
相关术语:
(1)节点的度:一个节点含有的子树的个数称为该节点的度;
(2)叶节点或终端节点:度为0的节点称为叶节点;
(3)非终端节点或分支节点:度不为0的节点;
(4)双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
(5)孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
(6)兄弟节点:具有相同父节点的节点互称为兄弟节点;
(7)树的度:一棵树中,最大的节点的度称为树的度;
(8)节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
(9)树的高度或深度:树中节点的最大层次;
(10)堂兄弟节点:双亲在同一层的节点互为堂兄弟;
(11)节点的祖先:从根到该节点所经分支上的所有节点;
(12)子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
(13)森林:由m(m>=0)棵互不相交的树的集合称为森林;
1.02 无序树:
树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
1.03 有序树:
这树中任意节点的子结点之间有顺序关系,这种树称为有序树;
1.04 二叉树:
每个节点最多含有两个子树的树称为二叉树;
和链表一样,动态数据结构 ;
树中结点的定义:
class Node{
E e;
Node left; //左孩子
Node right; //右孩子
}
二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:
(1)空二叉树——如图(a);
(2)只有一个根结点的二叉树——如图(b);
(3)只有左子树——如图©;
(4)只有右子树——如图(d);
(5)完全二叉树——如图(e)。
1.05 完全二叉树:
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
特点:
(1)叶子结点只可能在最大的两层上出现,对任意结点,若其右分支下的子孙最大层次为L,则其左分支下的子孙的最大层次必为L 或 L+1;
(2)只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现;
(3)对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1。 即度为1的点只有1个或0个
完全二叉树的性质:
(1)若i为奇数且i>1,那么tree的左兄弟为tree[i-1];
(2)若i为偶数且i<n,那么tree的右兄弟为tree[i+1];
(3)若i>1,tree的父亲节点为tree[i div 2];
(4)若2*i<=n,那么tree的左孩子为tree[2*i];若2*i+1<=n,那么tree的右孩子为tree[2*i+1];
(5)若i>n div 2,那么tree[i]为叶子结点(对应于(3));
(6)若i<(n-1) div 2.那么tree[i]必有两个孩子(对应于(4))。
(7)满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
(8)完全二叉树第i层至多有2^(i-1)个节点,共i层的完全二叉树最多有2^i-1个节点。
出于简便起见,完全二叉树通常采用数组而不是链表存储,其存储结构如下:
1.06 满二叉树:
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树。
应用:
(1)基于满二叉树的原地快速排序
(2)基于满二叉树二分K-means聚类并行推荐算法
(3)基于满二叉树的RA码交织器设计
满二叉树满足如下性质:
1、一个层数为k 的满二叉树总结点数为: 。因此满二叉树的结点数一定是奇数个。
2、第i层上的结点数为。
3、一个层数为k的满二叉树的叶子结点个数(也就是最后一层)。
1.07 哈夫曼树:
给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉
树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大
的结点离根较近。
哈夫曼树(霍夫曼树)又称为最优树.
1、路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长
度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
2、结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该
结点之间的路径长度与该结点的权的乘积。
3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
1.08 平衡二叉树
平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,
性质:
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
最小二叉平衡树的节点总数的公式:
F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci(斐波那契)数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。
平衡二叉树的常用实现方法:
(1)红黑树
红黑树是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
(2)AVL
AVL是最先发明的自平衡二叉查找树算法。在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
(3)Treap
Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是,Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap并不一定是。
(4)伸展树
伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。
(5)SBT
Size Balanced Tree(简称SBT)是一自平衡二叉查找树,是在计算机科学中用到的一种数据结构。相比红黑树、AVL树等自平衡二叉查找树,SBT更易于实现。SBT能在O(log n)的时间内完成所有二叉搜索树(BST)的相关操作,而与普通二叉搜索树相比,SBT仅仅加入了简洁的核心操作Maintain。由于SBT赖以保持平衡的是size域而不是其他“无用”的域,它可以很方便地实现动态顺序统计中的select和rank操作。
1.09 二分搜索树:
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
特点:
二叉树具有唯一根节点
二叉树中每个结点最多有两个孩子,没有孩子的结点称之为叶子结点
二叉树中每个结点最多有一个父亲结点,根节点没有父亲结点
二叉树具有天然递归结构,每个结点的左子树或右子树,也是一个二叉树
不平衡二叉树:极端的左或右都是二叉树,只不过退化成了线性表。
一个结点也叫二叉树,无非左右孩子都为null,null 空也是二叉树。
对于二分搜索树的每个结点的值而言
大于其左子树的所有结点的值
小于其右子树的所有结点的值
二分搜索树的实现:
-
定义类
public class BinarySearchTree<E extends Comparable<E>>
-
定义结点(内部类)
private class Node{
public E e;
public Node left;
public Node right;
public Node(E e){
this.e=e;
left=null;
right=null;
}
- 定义成员变量
private Node root;
private int size;
- 定义构造函数
public BinarySearchTree(){
root=null;
size=0;
}
- 获取数中元素的个数
public int size(){
return size;
}
- 判断树是否为空
public boolean isEmpty(){
return size==0;
}
- 添加元素
递归实现
public void add(E e){
root=add(root,e); //外界访问添加元素时 内部调用add(Node node,E e)方法
}
//递归实现 以node为当前的根节点,添加元素e并返回该树的根
private Node add(Node node,E e){
if(node==null){ //根节点为空时
size++;
return new Node(e);
}
if(e.compareTo(node.e)<0){ //要添加的节点的元素小于当前根节点的元素时
node.left=add(node.left,e); //在其左子树上添加元素 以左子树为根节点 递归实现
}
if(e.compareTo(node.e)>0){ //要添加的结点的元素大于当前根节点的元素时
node.right=add(node.rigth,e); //在其右子树上添加元素 以右子树为根节点 递归实现
}
return node; //返回该树的根
}
- 查询元素
递归实现
public boolean contains(E e){
return contains(root,e); //外界调用查询元素时 内部调用contains(Node node,E e)方法
}
//以node为根的树中是否包含元素e
private boolean contains(Node node,E e){
if(node==null){ //如果根节点为空 则没有树
return false;
}else if(e.compareTo(node.e)==0){ //查询元素等于当前节点的值
return true;
}else if(e.compareTo(node.e)<0){ //查询元素小于当前节点的值
return contains(node.left,e); //在当前节点的左子树中寻找查询的元素
}else{ //查询元素大于当前节点的值
return contains(node.rigth,e); //在当前右子树中寻找查询的元素
}
}
- 前序遍历 (DLR)(深度优先遍历)
递归实现
public void preOrder(){
preOrder(root); //内部调用preOrder(Node node)
}
//前序遍历以node为根的树
private void preOrder(Node node){
if(node==null){ //树为空
return;
}
System.out.println(node.e); //根节点
preOrder(node.left); //递归调用左子树
preOrder(node.right); //递归调用右子树
}
- 中序遍历 (LDR) (深度优先遍历)
递归实现
public void inOrder(){
inOrder(root); //内部调用inOrder(Node node)
}
private void inOrder(Node node){
if(node==null){ //树为空
return;
}
inOrder(node.left); //递归调用左子树
System.out.println(node.e); //根节点
inOrder(node.rigth); //递归调用右子树
}
- 后序遍历(LDR)(深度优先遍历)
递归调用
public void postOrder(){
postOrder(root); //内部调用postOrder(Node node)
}
private void postOrder(Node node){
if(node==null){ //树为空
return;
}
postOrder(node.left); //递归调用左子树
postOrder(node.right); //递归死奥用右子树
System.out.println(node.e);//根节点
}
- 层次遍历(广度优先遍历)
public void levelOrder(){
LinkedQueue<Node> queue=new LinkedQueue<Node>(); //队列实现
queue.enqueue(root); //将根节点进队
while(!queue.isEmpty()){ //循环实现 条件队列不为空
Node cur=queue.dequeue(); //定义指针cur,cui所指向的元素出队
Sysetem,out,println(cur.e); //显示cur的值
if(cur.left!=null){ //如果cur的左子树不为空 将左子树加入队列中
queue.enqueue(cur.left);
}
if(cur.right!=null){ //如果cur的右子树不为空 将右子树加入队列中
queue.enqueue(cur.right);
}
}
}
- 获取最小值
public E minmum(){
if(size==0){ //树为空时 抛异常
throw new IllegalArguementException("Tree is Empty");
}
return minmun(root).e;
}
private Node minmum(Node node){ //找到最小的结点 二叉搜索树的左子树<根节点<右子树
if(node.left==null){ //左子树为空 返回根节点
return node;
}else{ //左子树不为空 递归调用 寻找左子树的的最小值
minmum(node.left);
}
}
- 获取最大值
public E maxmum(){
if(size==0){ //树为空时 抛异常
throw new IllegalArguementException("Tree is Empty");
}
return maxmum(root).e;
}
private Node maxmun(Node node){ //找到最大的结点 二叉搜索树的左子树<根节点<右子树
if(node.rigth==null){ //如果右子树为空时 返回根节点
return node;
}else{ //如果右子树不为空时 递归调用maxmum(Node node)寻找最大值
return maxmun(node.rigth);
}
}
- 删除最小值
public E removeMin(){
E ret=minmun(); //获取最小元素
root=removeMin(root); //调用removeMin()方法
return ret;
}
private Node removeMin(Node node){
if(node.left==null){ //左子树为空
Node rigthNode=node.right; //rightNode指针指向node的右子树根节点
node.right=null; //node的右子树置空
size--;//有效值-1
return rigthNode; //返回node的右子树根节点 给上一级递归调用的node.left=removeMin(node.left); 位置
}
node.left=removeMin(node.left); //将rightNode的根节点接上要删除结点的的左子树
return node; //返回删除后的根节点
}
- 删除最大值
public E removeMax(){
E ret=maxmun(); //获取最大元素
root=removeMax(root); //调用removeMax()方法
return ret;
}
private Node removeMax(Node node){
if(node.right==null){
Node leftNode=node.left; //指针leftNode指向node左子树的根节点
node.left=null; //要删除的node结点左子树置为空
size--; // 有效长度-1
return leftNode; //返回给上一级node.right=removeMax(node.right);
}
node.right=removeMax(node.right); //将其左子树接到要删除结点的父亲结点的右子树上
return node; //返回删除后的根节点
}
- 删除任意值
public void remove(E e){
root=remove(root,e); //内部调用
}
private Node remove(Node node.E e){
if(node==null){ //树为空
return null;
}
if(e.compareTo(node.e)<0){ //删除的值比当前节点的值小
node.left=remove(node.left,e); //递归调用其左子树,删除其中的值 更新node的左子树
return node; //返回节点
}else if(e.compareTo(node.e)>0){ //删除的值比当前的结点的值大
node.right=remove(node.rigth,e); //递归调用其右子树,删除其中的值 更新node的右子树
return node; //返回节点
}else{ //找到元素
if(node.left==null){ //如果要删除的结点其左子树为空 和删除最小值方法一样
Node rightNode=node.right;
node.right=null;
size--;
return rightNode;
}
if(node.right==null){ //如果要删除的结点其右子树为空 和删除最大值方法一样
Node leftNode=node.left;
node.left=null;
size--;
return leftNode;
}
//当要删除的结点的左右子树都存在时
Node newcurRoot=minmum(node.right); //将其右子树的最小值作为要删除结点的替换值
newcurRoot.right=removeMin(node.right); //将删除最小值后的node的右子树接到替换值的右子树上
newcurRoot.left=node.left; //将其node的左子树接到替换值的左子树上
node.left=node.right=null; //令其node的左右子树都为空 断开原来的联系
return newcurRoot; //最后返回替换值 即返回重造的以替换值为根节点这个树 返回到leftNode或rightNode
}
}
- 打印
pubic StringtoString(){
StringBuilder res=new StringBuilder(); //创建字符串变量res
generateBSTString(root,0,res); //内部调用generateBSTString()方法
return res.toString();
}
private void generateBSTString(Node node,int depth,StringBuilder res){ //res组成树的元素
if(node==null){ //树为空
res.append(generateDepthString(depth)+"null\n"); //字符串中添加树的深度以“--”形式显示
return;
}
//前序遍历
res.append(generateDepthString(depth)+node.e+"null\n");
generateBSTString(node.left,depth+1,res);
generateBSTString(node.right,depth+1,res);
}
private String generateDepthString(int depth){ //res组成元素前面的层数 显示为“--”(与打印目录类似)
StrinfBuilder res=new StringBuilder();
for(int i=0;i<depth;i++){ //当前元素处于树的第几层 则打印几个“--”
res.append("--");
}
return res.toString(); //返回“--..”
}