树描述层次结构,是一种一对多的逻辑关系
树的定义
树是由n个结点组成的有限集合。
如果n=0,称为空树
如果n>0,则:
有一个特定的称之为根(root)的结点,它只有后继,但没有前驱;
除根以外的其它结点划分为m (m≥0)个互不相交的有限集合T0, T1, …, Tm-1,每个集合本身又是一棵树,并且称之为根的子树(subTree)。每棵子树的根结点有且仅有一个直接前驱,但可以有0个或多个后继。
树的表示方式
(a)树形表示
(b)凹入表
(c)文氏图
基本术语
度:
一个结点的子树个数称为该结点的度。一棵树的度是指该树中结点的最大度数。
叶子或终端结点:
度为0的结点称为叶子。度不为0的结点称为分支结点或非终端结点,除根结点之外的分支结点统称为内部结点。
孩子和双亲:
结点的子树的根称为该结点的孩子,该结点称为孩子的双亲或父亲。
兄弟:
同一个双亲的孩子彼此称作兄弟。
祖先和子孙:
结点的祖先是从根到该结点所经分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。
层数:
从根结点开始定义,根为第一层,其余结点的层数等于其双亲结点的层数加1。其双亲在同一层上的结点互为堂兄弟。
树的深度或高度:
树结点中的最大层数称为树的高度或深度。
无序树、有序树:
如果将树中结点的各子树看成从左到右是有次序的(即不能互换),则称该树为有序树。否则称为无序树。
结点的次序:
在有序树中可以从左到右地规定结点的次序。按从左到右的顺序,我们可以把一个结点的最左边的子结点简称为最左子结点,或直接称为长子,而把长子右边的结点称为次子。
森林:
是m(m>=0)棵互不相交的树的集合。删除一棵树的根就得到一个森林。
二叉树
(1)每个结点至多有二棵子树(即不存在度大于2的结点)
(2)二叉树的子树有左、右之分,且其次序不能任意颠倒,只有一棵子树时也必须分清左右子树。
性质:
性质1:一个非空二叉树的第i层上至多有2^(i-1)个结点(i≥0)
性质2:深度为k的二叉树至多有2^k-1个结点(k ≥1)
性质3:对任何一棵非空二叉树T,如果叶结点数为n0,度为2的结点数为n2,则n0=n2+1。
n0+n1+n2 = 所有结点的度数和+1 = n1+ 2*n2 +1
扩展一下可以得到:
满二叉树
度为k且有2^k-1个结点的二叉树,不存在度为1的结点。
编号方式为:从上到下,从左到右。
完全二叉树
深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树编号从1到n的结点一一对应时,称为完全二叉树。
特点:
(1)叶子结点只可能在层次最大的两层上出现。
(2)任一结点,若其左分支下的子孙的最大层次为h,则其右分支下的最大层次为h或h-1,即若结点无左子女,决不应有右子女。
性质4:具有n个结点的完全二叉树的深度k为log2n(取下界) +1
性质5:如果对一棵有n个结点的完全二叉树按层次次序从1开始编号,则对任一结点i(1 ≤i≤n),有:
1)i=1,序号结点i是根;
i>1, 其双亲结点是i/2(取下界)。
2)2i≤n,序号为i的结点的左子女结点的序号为2i;
2i>n,序号为i的结点无左子女。
3)2i+1≤ n,序号为i的结点的右子女结点的序号为2i+1;
2i+1>n,序号为i的结点无右子女。
完全二叉树的推论:
n个结点的完全二叉树中:
度为1的结点数为(n+1)%2;
度为0的结点数为(n+1)/2(取下界);
度为2的结点数为(n+1)/2(取下界)-1;
编号最大的分支结点是n/2(取下界);
编号最小的叶子结点是n/2(取下界)+1。
n个结点的二叉树:
高最多为n;
最低为log2n(取下界)+1 (完全二叉树)。
二叉树的遍历
先序遍历
先序遍历二叉树算法的框架是:
若二叉树为空,则空操作;
否则
访问根结点 (D);
先序遍历左子树 (L);
先序遍历右子树 ®。
先序遍历的递归算法
void PreOrderTraverse(BiTree T)
{
if(T)
{
printf("%c",T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
遍历结果: - + a * b - c d / e f
中序遍历
中序遍历二叉树算法的框架是:
若二叉树为空,则空操作;
否则
中序遍历左子树 (L);
访问根结点 (D);
中序遍历右子树 ®。
void InOrderTraverse(BiTree T)
{
if(T)
{
InOrderTraverse(T->lchild);
printf("%c",T->data);
InOrderTraverse(T->rchild);
}
}
遍历结果:a + b * c - d - e / f
后序遍历
后序遍历二叉树算法的框架是
若二叉树为空,则空操作;
否则
后序遍历左子树 (L);
后序遍历右子树 ®;
访问根结点 (D)。
void PostOrderTraverse(BiTree T)
{
if(T)
{
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
printf("%c",T->data);
}
}
遍历结果: a b c d - * + e f / -
//非递归以后再补吧
构造二叉树
读入一棵二叉树对应的扩充二叉树的前序遍历的结点值序列。每读入一个结点值就进行分析:
◆ 若是扩充结点值:令根指针为NULL;
◆ 若是(正常)结点值:动态地为根指针分配一个结点,将该值赋给根结点,然后递归地创建根的左子树和右子树。
int CreateBiTree(BiTree &T)
{
char ch;
scanf("%c",&ch);
if(ch=='@')
T=NULL;
else
{
T=(BiTree)malloc(sizeof(BiTNode));
T->data=ch;
CreateBiTree(T->lChild);
CreateBiTree(T->rChild);
}
}
二叉树深度
int Depth(BiTree T)
{
int depl,depr;
if(T==NULL)
return 0;
else
{
depl=Depth(T->lchild);
depr=Depth(T->rchild);
if(depl>=depr)
return depl+1;
else
return depr+1;
}
}
二叉树结点个数
int Size(BiTree T)
{
if(T==NULL)
return 0;
else
return (1+Size(T->lchild)+Size(T->rchild));
}
二叉树叶子结点个数
int LeafCount(BiTree T)
{
if(!T)
return 0; //空树没有叶子
else
if(T->lchild==NULL&&T->rchild==NULL)
return 1; //叶子结点
else
return LeafCount(T->lchild)+LeafCount(T->rchild);
//左子树叶子数加上右子树叶子数
}
层次遍历二叉树
//这个不能直接运行起来,要自己构建队列,也可以直接用库里的
typedef BiTNode* ElemType;
typedef struct
{
ElemType base[M];
int front,rear;
} SqQueue;
void LevelOrderTraverse(BiTree T)
{
BiTree p;
SqQueue Q;
Q.rear=Q.front=0;
if(T)
{
Q.base[Q.rear]=T;
Q.rear=(Q.rear+1)%MAXQSIZE;
while(Q.front!=Q.rear)
{
p=Q.base[Q.front];
printf("%c",p->data);
Q.front=(Q.front+1)%MAXQSIZE;
if (p->lchild)
{
Q.base[Q.rear]=p->lchild;
Q.rear=(Q.rear+1)%MAXQSIZE;
}
if (p->rchild)
{
Q.base[Q.rear]=p->rchild;
Q.rear=(Q.rear+1)%MAXQSIZE;
}
}
}
}
线索二叉树
二叉树遍历后,结点的前驱和后继信息能够反映出来
考察二叉链表表示中的空指针域:n+1个域是空,可以利用这些来体现
//后序和先序也能很容易画出来
线索二叉树的二叉链表表示(中序序列)
D B A E G C H F I
可以加头结点,但我没看出有啥用
相关概念
前驱与后继:在二叉树的先序、中序或后序遍历序列中两个相邻的结点互称为~
线索:指向前驱或后继结点的指针。
线索二叉树:加上线索的二叉链表表示的二叉树。
线索化:将二叉树变为线索二叉树的过程称为线索化。
按中序线索化二叉树:按中序遍历二叉树的顺序对二叉树进行线索化。
按中序遍历中序线索树:沿线索遍历。
线索二叉树的实现
在有n个结点的二叉链表中必定有n+1个空链域
在线索二叉树的结点中增加两个标志域
ltag:若 ltag =0, lchild 域指向左孩子;若 ltag=1, lchild域指向其前驱
rtag:若 rtag =0, rchild 域指向右孩子;若 rtag=1, rchild域指向其后继
typedef struct BiThrNode
{
ElemType data;
struct BiThrNode *lchild,*rchild;
int ltag,rtag;
}BiThrNode,*BiThrTree;
BiThrTree T;
中序线索化二叉树
BithrTree pre=NULL;
void InorderThread(BiThrTree p)
{
if(p!=NULL)
{
InorderThread(p->lchild);
if(p->lchild==NULL)
p->ltag=1;
if(p->rchild==NULL)
p->rtag=1;
if(pre!=NULL)
{
if(pre->rtag==1)
pre->rchild=p;
if(p->ltag==1)
p->lchild=pre;
}
pre=p;
InorderThread(p->rchild);
}
}
中序遍历中序线索二叉树
在中序线索二叉树中找结点后继的方法:
(1)若rtag=1, 则rchild域直接指向其后继
(2)若rtag=0, 则结点的后继应是其右子树的左链尾(ltag=1)的结点
在中序线索二叉树中找结点前驱的方法:
(1)若ltag=1, 则lchild域直接指向其前驱
(2)若ltag=0, 则结点的前驱应是其左子树的右链尾(rtag=1)的结点
void InorderTraverseThr(BiThrTree p)
{
while(p)
{
while(p->ltag==0)
p=p->lchild;
visited(p->data);
while(p->rtag==1)
{
p=p->rchild;
visited(p->data);
}
p=p->rchild;
}
}
先序遍历线索二叉树
后序遍历线索二叉树
树和森林
森林:是m(m>=0)棵互不相交的树所组成的集合。
树的存储结构
1 双亲表示法
typedef struct Ptnode{
ElemType info;
int parent;
}Ptnode;
typedef struct{
Ptnode nodes[MAX_NODE];
int n;
}Ptree;
typedef struct Ctnode{
int child;
struct Ctnode *next;
}*childlink;
typedef struct{
ElemType info;
childlink children;
}CTBox;
CTBox nodes[MAX_NODE];
typedef struct CSNode{
ElemType info;
struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
树,森林与二叉树的转换
将树转换成二叉树
步骤:
连线:在兄弟之间加一连线
切线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
旋转:以树的根结点为轴心,将整树顺时针转45°
将二叉树转换成树
步骤:
加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与p的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线
调整:将结点按层次排列,形成树结构
森林转换成二叉树
步骤:
将各棵树分别转换成二叉树
将每棵树的根结点用线相连
以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构
二叉树转换成森林
步骤:
抹线:将二叉树中根结点与其右孩子的连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
还原:将孤立的二叉树还原成树
树和森林的遍历
树的遍历(先根遍历、后根遍历、层次遍历)
先根遍历:先根访问树的根结点,然后依次先根遍历根的每棵子树
后根遍历:先依次后根遍历每棵子树,然后访问根结点
树的先根遍历 转换后的二叉树的先序遍历
树的后根遍历 转换后的二叉树的中序遍历
森林的遍历
(1)先根遍历
若森林F为空, 返回;否则
访问F的第一棵树的根结点
先根次序遍历第一棵树的子树森林
先根次序遍历其它树组成的森林
(2)后根遍历
若森林F为空,返回;否则
后根次序遍历第一棵树的子树森林
访问F的第一棵树的根结点
后根次序遍历其它树组成的森林
哈夫曼树
typedef struct{
int weight;
int parent,lchild,rchild;
}HufmTree;
HufmTree tree[2*n-1];
void huffman(HufmTree tree[],int n)
{
int i,j,x1,x2,n1,n2;
if(n<=1) return;
for(i= 0;i<2*n-1;i++)
{
/* 置初态 */
tree[i].lchild=-1;
tree[i].rchild=-1;
tree[i].parent=-1;
if(i<n)
tree[i].weight=w[i];
else
tree[i].weight=-1;
}
for(i=0;i<n-1;i++)
{
n1=MAXINT;
n2=MAXINT;
x1=-1;
x2=-1;
for(j=0;j<n+i;j++) /* 找两个最小权的无父结点的结点 */
if(tree[j].weight<n1&&tree[j].parent==-1)
{
n2=n1;
x2=x1;
n1=tree[j].weight;
x1=j;
}
else
if(tree[j].weight<n2&&tree[j].parent==-1)
{
n2=tree[j].weight;
x2=j;
}
tree[x1].parent=n+i;
tree[x2].parent=n+i;
tree[n+i].weight=n1+n2;
tree[n+i].lchild=x1;
tree[n+i].rchild=x2;
}
}