树
一.基本概念
树:是n个结点的有限集。n=0表示空树,任意一颗非空树中:(1)有且仅有一个特定的称为根的节点;(2)n>1时,其余结点可以分为m个不相交的有限集,每一集合本身又是一棵树,称为根的子树
结点:树的结点是一个包含数据元素以及若干指向其子树的分支
结点的度:结点拥有的子树数,如图结点B的度为1,C的度为2
叶结点:度为0的结点
分支结点:度不为0的结点
树的度:树内各结点的度的最大值,如图树的度为3
结点的孩子:结点的子树的根称为结点的孩子,相应的,该结点称为孩子的双亲
结点的层次:从根开始定义起,根为第一层,根的孩子为第二层,如图总共四层
树的高度或者深度:结点的最大层次,如图高度为4
有序树:如果将树中的结点的各个子树看成是从左到右是有次序的,不能互换的,则称该树为有序树,否则为无序树
森林:是m棵互不相交的数的集合
二.树的表示方法
- 双亲表示法
以一组连续空间存储树的结点,同时在每个结点中,附设一个指示双亲结点在数组中的位置 - 孩子表示法
每个结点有多个指针域,其中每个指针域指向一颗子树的根结点 - 孩子兄弟表示法
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的,因此,我们设置两个指针,分别指向该结点的第一个孩子和此节点的右兄弟
三.常见的树
- 二叉树
定义:由一个根结点和两颗互不相交的、分别称为根结点的左子树、右子树的二叉树组成。有五种基本形态:空二叉树、只有一个根结点、根结点只有左子树、只有右子树、既有左子树又有右子树。 二叉树的性质如下:
性质一:在二叉树的i层上至多有2^ (i-1)个节点
性质二:深度为k的二叉树至多有(2^k)-1个节点
性质三:对任何一棵二叉树T,如果终端结点树为n,度为2的结点为n2,度为0的结点为n0 则n0=n2+n1
性质四:具有n个节点的完全二叉树的深度为[log2n]+1向下取整
性质五:如果有一颗有n个节点的完全二叉树的节点按层次序编号,对任一层的节点i(1<=i<=n)有
1.如果i=1,则节点是二叉树的根,无双亲,如果i>1,则其双亲节点为[i/2],向下取整
2.如果2i>n那么节点i没有左孩子,否则其左孩子为2i
3.如果2i+1>n那么节点没有右孩子,否则右孩子为2i+1
有三种特性形态的二叉树:
- 斜树
所有的结点都只有左子树叫做左斜树,同理有右斜树,左斜树与右斜树统称为斜树
- 满二叉树
在一棵二叉树中,如果所有的分支结点都存在左子树和右子树,并且所有的叶子结点都在同一层上,这样的二叉树称为满二叉树
- 完全二叉树
对一颗具有n个结点的二叉树按照层序编号,如果编号为i的结点与同样深度的满二叉树中编号为i的结点位置相同,则称这棵二叉树为完全二叉树
- 斜树
- 二叉查找/排序树(BST)
二叉查找树又称二叉排序树、二叉搜索树,它或者是一棵空树,或者是具有下列性质的二叉树:
- 若它的左子树不为空,则左子树上的所有结点的值均小于根结点的值;
- 若它的右子树不为空,则右子树上的所有结点的值均大于根结点的值;
- 它的左右子树也分别为二叉排序树
- 没有键值相等的结点
- 平衡二叉树(AVL树)
平衡二叉树,又称AVL树,是一种二叉排序树,其中的每一个结点的左子树和右子树的高度差至多等于1,一种高度平衡的二叉排序树。
将二叉树上结点的左子树深度减去右子树的深度的值,称为平衡因子BF,AVL树上的所有结点的BF值只可能是-1,0,1。距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,称为最小不平衡子树。
- 红黑树(RB树)
一棵由n个结点构造的完全、二叉查找树的高度为logn+1,一般查找效率为O(logn),但若二叉查找树退化成一棵具有n个结点的线性链后,查找效率最坏为O(logn)。
因此,出现了AVL、红黑树。AVL树是最早的平衡树之一,一种高度平衡的二叉查找树,但往往维护这种高度平衡所付出的代价比从中获取的收益还要高,故实际应用并不多(在插入删除不频繁,只是对查找有要求的场景中AVL树还是优于红黑树的)。
红黑树是一种自平衡二叉查找树,与AVL树一样,对插入时间、删除时间、和查找时间提供了最好可能和最坏情况担保。但红黑树牺牲了高度平衡的优越条件为代价,只要求部分平衡,从而降低了对旋转的要求,提供了性能。如java的TreeMap/C++ STL中的map、进程控制块等都用红黑树来实现。
红黑树是每个节点都带有颜色的二叉查找树,颜色为红色或者黑色,具有以下性质:
- 性质1:节点是红色或者黑色
- 性质2:根结点是黑色
- 性质3:所有的叶子结点都是黑色的空节点(NIL)
- 性质4:每个红色的结点必须有两个黑色的子节点(从叶子到根的所有路径不能有两个连续的红色结点)
- 性质5:从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色结点
红黑树的自平衡的调整、插入、删除等涉到多种情况,详细请参考:教你透彻了解红黑树
B(B-树)树
B树也是一种用于查找的平衡树,但是不是二叉树。
定义:B树是一种树状数据结构,能够用来存储排序后的数据。这种数据结构能让查找数据、循环存取、插入以及删除动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树,可以拥有多于两个的子结点。与自平衡二叉查找树,B树为系统最优化大块数据的读和写操作。B树减少定位记录时所经历的中间过程,从而加快存取速度。常被用在数据库和文件系统上。
在B树中查找给定关键字的方法是:首先取出根结点,在根结点所包含的关键字K1…….Kn查找给定的关键字(顺序或者二分查找),若找到给定关键字,则查找成功,否则,一定可以确定要查找的关键字在Ki与Ki+1之间,Pi为指向子树根节点的指针,此时,取指针Pi所指的结点继续查找,直到找到,或者指针Pi为空时查找失败。
B树作为一种多路搜素树(非二叉):- 定义任意非叶子结点最多只有M个儿子,且M>2;
- 根结点的儿子数为[2,M];
- 除根结点以外的非叶子结点的儿子数为[M/2,M];
- 每个结点存放至少M/2-1(取上整)和至多M-1个关键字;
- 非叶子结点的关键字个数 = 指向儿子的指针个数 - 1;
- 非叶子结点的关键字:K[1],k[2],…,K[M-1],且K[i]小于K[i+1];
- 非叶子结点的指针:P[1],P[2],……P[M];其中P[1]指向关键字小于K[1]的子树, P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
- 所有的叶子结点位于同一层
B树的特性:
- 关键字集合分布在整个树中
- 任何一个关键字出现且只出现在一个结点中
- 搜索有可能在非叶子结点结束
- 其搜索性能等价于在关键字全集内做一次二分查找
- 自动层次控制
B+树
B+树是B树的变体,也是一种多路搜素树,其定义基本与B树相同,除了:- 非叶子结点的子树指针与关键字个数相同
- 非叶子结点的子树指针P[i],指向关键字值属于[k[i],k[i+1]]的子树(B树为开区间)
- 为所有叶子结点增加一个链指针
- 所有关键字都在叶子结点出现
B+树的搜索与B树叶基本相同,区别是B+树只有到达叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找。
B+树的性质主要有:
- 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字也是有序的
- 不可能在非叶子结点命中
- 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储数据的数据层
- 更适合文件索引系统
Trie树
Trie树称为字典树,又称为单词查找树,是一种树形结构,是一种哈希树的变种,主要用于统计、排序和保存大量的字符串,所以经常被搜索引擎系统用于文本词频统计。优点是:利用字符串的公共前缀来减少查询时间,最大限度的减少无畏的字符串比较,查询效率比较高。主要性质有:- 根结点不包含字符,除根结点以外的每一个结点都只包含一个字符
- 从根结点到某一个结点,路径上经过的字符连接起来,为该节点对于的字符串
- 每个结点的所有子节点包含的字符都不相同
Trie树主要有以下应用:
- 串的快速检索
如“给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词” - “串排序”
如“给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出” - 最长公共前缀
如“对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为求公共祖先的问题”
- 赫夫曼树
从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路劲上分支的数目称为路径长度,如图N2到A的路径长度为2。树的路径长度就是从树根到每一个结点的路径长度之和,如图树的路径长度为1 + 1+ 2 + 2=6。如果考虑带权的结点,结点的带权的路径长度为从该节点到树根之间的路径长度与结点上的权乘积。假设有n个权值{w1,w2,…,wn},构造一棵有n个叶子结点的二叉树,每个叶子结点带权wk,每个叶子结点的路径长度为lk,则其中带权路径长度WPL最小的二叉树称为赫夫曼树,也成为最优二叉树。如图WPL = 1*15+5*2+10*2 = 45
赫夫曼编码:一般地,设需要编码的字符集为{d1,d2,…dn},各个字符在电文中出现的次数或者频率集合为{w1,w2,…,wn},以{d1,d2,…dn}作为叶子结点,以w1,w2,…,wn作为相应叶子结点的权值来构造一棵赫夫曼树。规定赫夫曼树的做分支代表0,右分支代表1,则从根结点到叶子结点的所经过的路径分支组成的0和1序列便为该结点对应字符的编码,即赫夫曼编码,也是一种最基本的压缩编码方法。
四.相关算法
1 二叉树的存储结构
①顺序存储结构
用一维数组存储二叉树中的结点,一般只用于完全二叉树的存储
②链式存储结构
typedef struct BiTNode
{
TElemType data; //节点数据
struct BiTNode *lchild; //指向左孩子指针
struct BiTNode *rchild; //指向右孩子指针
}BiTNode, *BiTree;
2 二叉树的创建
void createBiTree(BiTree *T)//前序遍历序列创建的二叉树
{
char ch;
scanf("%c",&ch);
if (ch == '#')
{
*T = NULL;
}
else
{
*T = (BiTree)malloc(sizeof(BiTNode));
if (!*T)
{
exit(-1);
}
else
{
(*T)->data = ch;
createBiTree(&(*T)->lchild);
createBiTree(&(*T)->rchild);
}
}
}
int main()//测试代码
{
//ABG##DE##F##C##
printf("%s\n","This is my test code!");
BiTree T = NULL;
createBiTree(&T);
return 0;
}
3 二叉树的遍历
void orderTraverse(BiTree T)
{
if (NULL == T)
{
return;
}
printf("%c",T->data);//前序遍历
orderTraverse(T->lchild);
//printf("%c", T->data);//中序遍历
orderTraverse(T->rchild);
//printf("%c", T->data);//后序遍历
}
public void levelTraverse(BiTree T)//层序遍历(java版)
{
if(T == null){
return ;
}
LinkedList<BiTree> queue = new LinkedList<BiTree>();
BiTree current = null;
queue.offer(root);//将根节点入队
while(!queue.isEmpty())
{
current = queue.poll();//出队队头元素并访问
System.out.print(current.val +"-->");
if(current.left != null){//如果当前节点的左节点不为空入队
queue.offer(current.left);
}
if(current.right != null){//如果当前节点的右节点不为空入队
queue.offer(current.right);
}
}
}
4 二叉树的最小深度、最大深度
int BTNodeDepth(BiTree T)//二叉树的最大深度
{
int lchilddep, rchilddep;
if (T == NULL)
return 0;
else
{
lchilddep = BTNodeDepth(T->lchild);
rchilddep = BTNodeDepth(T->rchild);
}
return lchilddep > rchilddep ? (lchilddep + 1) : (rchilddep + 1);
}
5 二叉搜索树的查找、插入与删除
BiTree search_BST(BiTree T, int key)//查找(非递归版本)
{
while ((T != NULL) && (T->data != key))
{
if (key < T->data)
T = T->lchild;
else
T = T->rchild;
}
return T;
}
BiTree Insert(BiTree T, int x)//插入(递归版本)
{
if (!T)//二叉树为空
{
T = (BiTree)malloc(sizeof(BiTNode));
T->data = x;
T->lchild = T->rchild = NULL;
}
else
{
if (x < T->data)//小于,在左子树插入
T->lchild = Insert(T->lchild, x);
else if (x > T->data)//大于,在右子树上插入
T->rchild = Insert(T->rchild, x);
}
return T;
}
BiTree Delete(BiTree T, int x)//删除
{
BiTree Tmp;
if (T == NULL)
printf("Not Found\n");
else
{
if (x < T->data)
T->lchild = Delete(T->lchild, x);
else if (x > T->data)
T->rchild = Delete(T->rchild, x);
else//考虑如果找到这个位置,并且有左节点或者右节点或者没有节点三种情况
{
if (T->lchild && T->rchild)
{
Tmp = FindMin(T->rchild); /* 在右子树中找到最小结点填充删除结点 */
T->data = Tmp->data;
T->rchild = Delete(T->rchild, T->data);/* 递归删除要删除结点的右子树中最小元素 */
}
else
{ /* 被删除结点有一个或没有子结点*/
Tmp = T;
if (!T->lchild)
T = T->rchild; /*有右孩子或者没孩子*/
else if (!T->rchild)
T = T->lchild;/*有左孩子或者没有孩子*/
free(Tmp); /*如无左右孩子直接删除*/
}
}
}
return T;
}
BiTree FindMin(BiTree T)
{
if (T != NULL)
{
while (T->lchild)
T = T->lchild;
}
return T;
}
6 AVL树相关算法
#define EH 0 //等高
#define LH 1 //左高
#define RH -1 //右高
typedef struct _BitNode //数据结构
{
int data;
int bf;//平衡因子
struct _BitNode *lchild,*rchild;
}BitNode,*BiTree;
void R_Rotate(BiTree&T)//右旋转
{
BiTree p;
p=T->lchild;
T->lchild=p->rchild;
p->rchild=T;
T=p;
}
void L_Rotate(BiTree&T)//左旋转
{
BiTree p;
p=T->rchild;
T->rchild=p->lchild;
p->lchild=T;
T=p;
}
void LeftBalance(BiTree&T)//左平衡函数,右平衡类似
{
BiTree L,lr;
L=T->lchild;
switch (L->bf)
{
case EH:
L->bf=RH;
T->bf=LH;
R_Rotate(T);
break;
case LH:
L->bf=T->bf=EH;
R_Rotate(T);
break;
case RH:
lr=L->rchild;
switch (lr->bf)
{
case EH:
L->bf=L->bf=EH;
case RH:
T->bf=EH;
L->bf=LH;
break;
case LH:
L->bf=EH;
T->bf=RH;
break;
default:
break;
}
lr->bf=EH;
L_Rotate(T->lchild);
R_Rotate(T);
break;
default:
break;
}
}
bool InsertAVLtree(BiTree&T,int key,bool&taller)//插入
{
if(!T)//此树为空
{
T=new BitNode; //直接作为整棵树的根。
T->bf=EH;
T->lchild=T->rchild=NULL;
T->data=key;
taller=true;
return true;
}
else
{
if(key==T->data) //已有元素,不用插入了,返回false;
{
taller=false;
return false;
}
if(key<T->data) //所插元素小于此根的值,就找他的左孩子去比
{
if(!InsertAVLtree(T->lchild,key,taller))//所插元素小于此根的值,就找他的左孩子去比
return false;
if(taller) //taller为根,则树长高了,并且插入到了此根的左子树上。
{
switch (T->bf)//此根的平衡因子
{
case EH: //原先是左右平衡,等高
T->bf=LH; //由于插入到左子树上,导致左高=》》LH
taller=true; //继续往上递归
break;
case LH:
LeftBalance(T);//原先LH,由于插入到了左边,这T这个树,不平衡需要左平衡
taller=false;//以平衡,设taller为false,往上递归就不用进入此语句了,
break;
case RH:
T->bf=EH;//原先RH,由于插入到左边,导致此T平衡
taller=false;
break;
default:
break;
}
}
}
else
{
if(!InsertAVLtree(T->rchild,key,taller))
return false;
if(taller)
{
switch (T->bf)
{
case EH:
T->bf=RH;
taller=true;
break;
case LH:
T->bf=EH;
taller=false;
break;
case RH:
RightBalance(T);
taller=false;
break;
default:
break;
}
}
}
}
参考:
https://www.cnblogs.com/maybe2030/p/4732377.html
https://blog.csdn.net/mtt_sky/article/details/51442452
http://lib.csdn.net/article/datastructure/9204