目录
树的定义和基本术语
树是n(n>=0)个结点的有限集。n=0时,为空树
特点:唯一前驱(根),多个后继。
子树:当n>1时,其余结点可分为m个互不相交的有限集,其中每一个有限集又是一颗树称为子树
树的表示方法:
- 嵌套集合
- 凹入表示法
- 广义表表示法
结点:树中每一个数据元素及若干指向其子树的分支
结点的度:结点拥有的子树个数
叶子(终端结点):度为0的结点
分支结点(非终端结点):度不为0的结点
树的度:树内各节点的度的最大值
孩子:结点的子树的根更为该结点的孩子
双亲:该节点则称为孩子的双亲
兄弟:同一根的孩子
堂兄弟:双亲同一根的孩子
子孙:根下的任一结点
深度:树中结点的最大层次称为树的深度
有序树:树中结点的各子树看成从左至右是有次序的(不可交换)
无序树:树中结点的各子树没有次序
森林:m棵互不相交的树的集合
二叉树
定义:每个结点至多只有两颗子树,两颗子树不可随意调换
二叉树性质
性质1 在二叉树的第i层上至多有2**(i-1)个结点(i>=1)
性质2 深度为k的二叉树至多有2**k-1个结点,(k>=1)等比数列
性质3 对任何一棵二叉树T,如果其终端节点数为n0,度为2的结点数位n2,则n0=n2+1
证明:
终端结点:n0 度为1的结点:n1 度为2的结点:n2 总结点:n 总分支:B
n=n0+n1+n2
n=B+1
B=n1+2n2
n=n1+2n2+1
n0=n2+1
性质4 具有n个结点的完全二叉树的深度为[log(2)n]+1
证明:深度为k。总结点n
2**(k-1)-1<n<=2**k-1
k-1<=log(2)n<k
k=[log(2)n]+1
性质5 如果对一颗有n个结点的完全二叉树的结点按层序编号,则任一结点,有
(1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2].
(2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i
(3)如果2i+1>n,则结点i无右孩子(结点i为叶子结点);否则其左孩子是结点2i+1
二叉树的存储结构
顺序存储结构
#define MAX_TREE_SIZE 100
typedef TElemType SqBiTree[MAX_TREE_SIZE];
SqBiTree bt;
链式存储结构
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
二叉树的遍历实现
先序遍历
根左右
status PreOrderTraverse(BiTree T){
if(T==NULL)
{
return 0;
}
else{
printf("%d",T->data);//访问根结点
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
中序遍历
左根右
status PreOrderTraverse(BiTree T){
if(T==NULL)
{
return 0;
}
else{
PreOrderTraverse(T->lchild);
printf("%d",T->data);
PreOrderTraverse(T->rchild);
}
}
后序遍历
左右根
status PreOrderTraverse(BiTree T){
if(T==NULL)
{
return 0;
}
else{
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
printf("%d",T->data);
}
}
空间复杂度:N
时间复杂度:N
中序遍历非递归算法
Stutus InOrderTraverse(BiTree T){
BiTree p;
initStack(S);//初始化一个栈
p=T;
while(p||!StackEmpty(S)){
if(p)
{
Push(S,p);
p=p->lchild;
}
else
{
Pop(S,q);
printf("%c",q->data);
p=q->rchild;
}
}
return 0;
}
层次遍历
viod LevelOrder(BTNode *b){
BTNode *p;
SqQueue *qu;
InitQueue(qu);//初始化队列
enQueue(qu,b); //根结点指针进入队列
while(!QueueEmpty(qu)){ //队不为空,则循环
deQueue(qu,p); //出队结点p
printf("%c",p->data); //访问结点p
if (p->lchild!=NULL)
{
enQueue(qu,p->lchild);
}//有左孩子时将其进队
if (p->rchild!=NULL)
{
enQueue(qu,p->rchild);
}//有右孩子时将其进队
}
}
遍历算法应用
创建二叉树:
二叉树:ABC##DE#G##F###
Status CreateBiTree(BiTree &T){
scanf(&ch);
if(ch=="#"){
T=NULL;
}
else{
if(!(T=(BiTNode *)malloc(sizeof(BiTNode))))
{ exit(OVERFLOW);}
T->data=ch;
CreateBiTree(T->lchild);
CtreateBiTree(T->rchild)
}
return OK;
}
复制二叉树:
int Copy(BiTree T BiTree &NewT){
if(T==NULL){
NewT=NULL;
return 0;
}
else{
NewT=new BiTNode;
NewT->data=T->data;
Copy(T->lChild,NewT->lchild);
Copy(T->rChild,NewT->rchild);
}
}
计算深度:
思路:
- 判断是否为空树,深度为0
- 否则递归计算左子树的深度为m,递归计算右子树的深度n,比较然后加一
int Depth(BiTree T){
if(T==NULL) return 0;
}
else{
m=Depth(T->lChild);
n=Depth(T->rChild);
if(m>n){
return (m+1);
}
else{
return (n+1);
}
}
计算二叉树结点总数
思路:
- 如果是空树,则结点个数为0;
- 否则,结点个数为左子树的结点个数+右子树的结点个数+1;
int NodeCount(BiTree T){
if(T==NULL){
return 0;
}
else
return NodeCount(T->lChild)+NodeCount(T->rchild)+1;
}
计算叶子结点个数:
思路:
- 空数,叶子个数为0;
- 否则,为左子树的叶子个数+右子树的叶子个数
int LeadCount(BiTree T){
if(T==NULL) return 0;
if(T->lchild==NULL&&T->rchild==NULL){
return 1;
}
else{
return LeafCount(T->lchirl)+LeafCount(T->rchild);
}
}
线索二叉树
利用二叉树链表中的空指针域:
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱。如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继
这种改变指向的指针称为“线索”
加上了线索的二叉树称为线索二叉树
对二叉树按某种遍历次序使变为线索二叉树的过程叫线索化
标志域:itag和rtag。为标识rchild和lchild是指向孩子的指针还是指向前驱或者后继的指针。
规定:
ltag=0 lchild指向该结点的左孩子
ltag=1 lchild指向该结点的前驱
rtag=0 lchild指向该结点的右孩子
rtag=1 lchild指向该结点的后驱
循环二叉树:增加一个头节点
ltag=0,lchid指向根节点;rtag=1,rchild指向遍历序列中的最后一个结点;遍历序列中第一个结点的lc域和最后一个结点的rc域都指向头节点
树的存储结构
双亲表示法
实现:定义结构数组存放树的结点,每个结点含两个域:
- 数据域:存放结点本身信息
- 双亲域:指示本结点的双亲结点在数组中的位置。
//结点结构:
typedef struct PTNode{
TElem Type data;
int parent;//双亲位置域
}PTNode;
//树的结构:
#define MAX_TREE_SIZE 100
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int r,n; //根结点的位置和结点个数
}PTree;
孩子链表
把每个结点的孩子结点排列起来,看成是一个线性表,单链表存。则n个结点有n个孩子链表。而n个头指针又组成一个线性表,用顺序表存储
#孩子结点:
typedef srtuct CTNode{
int child;
struct CTNode *next;
}*ChildPtr;
#双亲结点:
typedef struct{
TElemType data;
ChildPtr firstchild;//孩子链表头指针
}CTBox;
二叉表表示法------孩子兄弟表示法
实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild *nextsibling;
}CSNode,*CSTree;
树与二叉树的转换
- 将树转化为二叉树进行处理,利用二叉树的算法来实现对树的操作
- 由于树和二叉树都可以用二叉链表作存储结构,则以二叉链表作媒介可以导出树与二叉树之间的一个对应关系
将树转换成二叉树
- 加线:在兄弟之间加一连线
- 抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
- 旋转:以树的根节点为轴心,将整树顺时针转45度
口诀:
树变二叉树:兄弟相连留长子
将二叉树转换成树
- 加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子,右孩子.......沿分支找到的所有右孩子,都与p的双亲用线连起来
- 抹线:抹掉原二叉树中双亲与右孩子之间的连线
- 调整:将结点按层次排列,形成树结构
口诀:
二叉树变树:
左孩子右右连双亲,去掉原来右孩线
森林转换转换成二叉树
- 将各棵树分别转换成二叉树
- 将每棵树的根结点用线相连
- 以第一棵树根节点为二叉树的根,在以根节点为轴心,顺时针旋转,构成二叉树型结构
口诀:
森林变二叉树:树变二叉根相连
森林与二叉树的转化
- 抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
- 还原:将孤立的二叉树还原成树
口诀:
二叉树变森林:去掉全部右孩线,孤立二叉再还原。
树的遍历
- 先根遍历:若树不空,,则先访问根结点,然后依次先跟遍历各课子树
- 后根遍历:若树不空,,则先依次跟遍历各棵子树,然后访问根结点
- 按层次遍历:若树不空,则自上而下自左至右访问树中每个结点
森林的遍历
将森林看作由三部分构成:
- 森林中第一棵树得根节点
- 森林中第一棵树的子树森林
- 森林中其他树构成的森林
先序遍历:
若森林不空,则
- 访问森林中第一棵树的根节点
- 先序遍历森林中第一棵树的子树森林
- 先序遍历森林中(除第一棵树之外)其余树构成的森林
即:依次从左至右对森林中的每棵树进行先根遍历