树的定义
树(Tree)是n(n>=0)个结点的有限集。当n=0时称为空树,在任意一颗非空树中:
有且仅有一个特定的称之为根(root)的结点;
当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,...,Tm,其中每一 个集合本身又是一棵树,并且称为根的子树(SubTree)
n>0时,根节点是唯一的,坚决不可能存在多个根节点
m>0时,子树的个数是没有限制的,但是它们相互是一定不会相交的
树的度:结点拥有的子树的个数称为结点的度,树的度取树内各节点的度的最大值。
度为0的结点称为叶节点(Leaf)或终端结点
度不为0的结点称为分支结点或非终端结点,出根结点外,分支结点也称为内部结点
某一结点的子树的根称之为该结点的孩子(Child),相应的,该结点称为孩子的双亲 (Parent),同一双亲的孩子之间互称为兄弟(Sibling)。结点的祖先是从根到该节点所 经分支上的所有结点。
结点的层次(Level)从根开始,根为第一行,根的孩子为第二行,以此类推。其双亲 在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。
树的存储结构
双亲表示法:一种以双亲作为索引关键词的存储方式
我们假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示其双亲结点 在数组中位置的元素
存储结构的设计是一个非常灵活的过程,只要你愿意,你可以设计出任何你想要 的奇葩! 一个存储结构设计得是否合理,取决于基于该存储结构的运算是否适合、是否方便、时 间复杂度好不好等等。
二叉树的定义
二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集,或者由 一个根节点和两棵互不相交的,分别称为根结点的左子树和右子树的二叉树组成。
每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点
左子树和右子树是有顺序的,次序不能颠倒
即使树中某结点只有一棵子树,也要区分它是左子树还是右子树
特殊的二叉树
(1)斜树
(2)满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层 上,这样的二叉树称为满二叉树
(3)完全二叉树
叶子结点只能出现在最下两层
最下层的叶子一定集中在左部连续位置
倒数第二层,若有叶子节点,一定都在右部连续位置。
如果结点度为1,则该节点只有左孩子 同样结点树的二叉树,完全二叉树的深度最小
满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树
二叉树的性质
性质一:在二叉树的第i层上至多有2^(i-1)个结点(i>=1)
性质二:深度为k的二叉树至多有2^k - 1 个结点(k>=1)
性质三:对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则 n0=n2+1
性质四:具有n个结点的完全二叉树的深度为[log2n]+1
性质五:如果对一棵有n个结点的完全二叉树的结点按层序编号,对任一结点 i(1<=i<=n): 如果i=1,则结点是二叉树的根,无双亲;若i>1,则其双亲结点为[i/2] 如果2i>n,则结点i无左孩子;否则左孩子是结点2i 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1
二叉树的存储结构
很难单单只用顺序存储结构或链式存储结构来存放。
但是二叉树是一种特殊的树,由于它的特殊性,使得用顺序存储结构或链式存储结构都 能够简单实现。
二叉树的顺序存储结构就是用一维数组存储二叉树中的各个结点,并且结点的存储位置 能体现结点之间的逻辑关系。
既然顺序存储方式的适用性不强,那么我们就要考虑链式存储结构。二叉树的存储按照 国际惯例来说一般也是采用链式存储结构的。
二叉树每个结点最多有两个孩子,所以为它涉及一个数据域和两个指针域是比较自然的 想法,我们称这样的链表叫作二叉链表。
// BiTree
#include<stdio.h>
#include<stdlib.h>
typedef char ElemType;
typedef struct Node{
ElemType data; // 结点数据域
struct Node *lchild; // 左孩子指针域
struct Node *rchild; // 右孩子指针域
}Node,*BiTree;// BiTree 是该结构体指针
// AB#D##C##
// 前序建立二叉树
void createTree(BiTree *T){ //指向根结点指针的指针 指向孩子指针的指针
char c;
scanf("%c",&c);
if(c=='#'){
*T=NULL;
}else{
*T=malloc(sizeof(Node));
(*T)->data=c;
createTree(&((*T)->lchild)); //左孩子
createTree(&((*T)->rchild)); //右孩子
}
}
void preTravser(BiTree T,int level){
if(T){
for(int i=1;i<=level;i++){
printf("");
}
printf("%c\n",T->data);
preTravser(T->lchild,level+1);
preTravser(T->rchild,level+1);
}
}
void mian(){
BiTree T=NULL; //结构体指针 T 开始为NULL
createTree(&T);
preTravser();// 前序遍历
}
线索二叉树
解决了上述二叉树遍历的缺点 浪费空间 n个结点 2n个指针域 + n个结点有n-1条分支=>2n-(n-1)=n+1个空指针域
#include<stdio.h>
#include<stdlib.h>
#define Link 0 //表示左右孩子
#define Thread 1 //表示前驱后继
typedef char ElemType;
typedef int PointerTag;
typedef struct Node{
ElemType data;
struct Node *lchild;
struct Node *rchild;
PointerTag ltag;//Link Thread
PointerTag rtag;//Link Thread
}Node,*BiThreadTree;
void createTree(BiThreadTree *T){
char c;
scanf("%c",&c);
if(c=='#'){
*T=NULL;
}else{
*T=malloc(sizeof(Node));
(*T)->data=c;
(*T)->ltag=Link;
(*T)->rtag=Link;
createTree(&(*T)->lchild);
createTree(&(*T)->rchild);
}
}
void visit(char c,int level){
for(int i=0;i<level;i++){
printf(" ");
}
printf("%c\n",c);
}
void preOrderTraverse(BiThreadTree T,int level){
if(T!=NULL){
visit(T->data,level);
preOrderTraverse(T->lchild,level+1);
preOrderTraverse(T->rchild,level+1);
}
}
BiThreadTree pre;//刚刚访问过的结点
//中序遍历线索化
void inThreading(BiThreadTree T){
if(T){
inThreading(T->lchild);//递归左孩子线索化
//结点处理
if(!T->lchild){
T->ltag=Thread;//线索
T->lchild=pre;//前驱
}
if(!pre->rchild){
pre->rtag=Thread;
pre->rchild=T;
}
pre=T;
inThreading(T->rchild);//递归右孩子线索化
}
}
void inOrderThreading(BiThreadTree *p,BiThreadTree T){
*p=malloc(sizeof(Node));
(*p)->ltag=Link; //左孩子
(*p)->rtag=Thread; //后继
(*p)->rchild=*p;
if(!T){
(*p)->lchild=*p;
}else{
(*p)->lchild=T;//将树的根节点赋给 P的左孩子
pre=*p;
inThreading(T);//开始对二叉树进行中序线索化
pre->rchild=*p;
pre->rtag=Thread;
(*p)->rchild=pre;
}
}
void inOrderTraverse(BiThreadTree P){
BiThreadTree p;
p=P->lchild;
while(p!=P){
while(p->ltag==Link){
p=p->lchild;
}
printf("%c",p->data);
while(p->rtag==Thread&&p->rchild!=P){
p=p->rchild;
printf("%c",p->data);
}
p=p->rchild;
}
}
void main(){
BiThreadTree P,T=NULL;//T是根节点的指针
createTree(&T);
preOrderTraverse(T,1);
inOrderThreading(&P,T);
printf("\n");
inOrderTraverse(P);
}
//ABD##E##C#F##
如果所用的二叉树需要经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用 线索二叉树的存储结构就是非常不错的选择。
哈弗曼树
从低到高依次排序
哈弗曼树的定义与原理
从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目 称为路径长度。
二叉树a中,根节点到D的路径为4;二叉树b中,根节点到D的路径为2
树的路径长度就是从树根到每一个结点的路径长度之和。
二叉树a中,1+1+2+2+3+3+4+4=20;二叉树b中,1+2+2+3+3+1+2+2=16
如果考虑到带权的结点,结点的带权路径长度从该结点的树根之间的路径长度与结点上 的乘积。
树的带权路径长度为树中所有叶子结点的带权路径长度之和。
带权路径长度WPL最小的二叉树为哈弗曼树。
注意:若要设计长短不等的编码,则必须是任一个字符的编码都不是另一个字符的编码 的前缀,这种编码称作前缀编码。但是,在解码时,还是要用到哈夫曼树,即发送发和接收 方必须要约定好同样的哈夫曼编码规则。