一、二叉树的创建
二叉树结构体的定义
typedef char ElemType;//定义数据类型
typedef struct BiNode
{
ElemType data;
struct BiNode* lchild;//左孩子
struct BiNode* rchild;//右孩子
}BiNode,*BiTree;
1、前序递归创建
递归是一种比较难理解的算法,但是既然能递归,就说明有一定的内在规律,所以理解的时候可以简化,把二叉树简化成3个结点,那么创建完A结点,再将左孩子指针作为形参传入就可以创建B结点,同理创建C结点。
void CreateBiTree(BiTree *T)//传入的是一个二级指针
{
ElemType ch;
scanf("%c",&ch);
if(ch == '#')
*T = NULL;
else
{
*T = (BiTree)malloc(sizeof(BiNode));
(*T)->data = ch;
CreateBiTree(&(*T)->lchild);//T->lchild是一级指针,要传入它的地址
CreateBiTree(&(*T)->rchild);
}
}
2、读边法创建创建
队列的存储结构
//队列
typedef struct node
{
BiNode* data;
struct node* next;
}QNode,*Qptr;
typedef struct
{
Qptr front,rear;
}Queue;
输入双亲节点、孩子结点和rlflag,rlflag=0为左孩子,rlflag=1为右孩子。
利用队列,首先对初始化的结点进队,然后如果这个结点的双亲结点是’#',那么这个为头结点,然后再进入一个结点,通过判断队头的结点是否为该结点的双亲结点,是则根据rlflag连接,否则不断出队,直到队头结点是该结点的双亲结点。
void Create_Edge(BiTree *T)
{
BiNode *p,*s;
Queue Q;
ElemType fa,ch;
int rlflag;
IniteQueue(&Q);
scanf("%c%c%d",&fa,&ch,&rlflag);
getchar();
while(ch != '#')
{
p = (BiNode*)malloc(sizeof(BiNode));
p->data = ch;
p->lchild = NULL;
p->rchild = NULL;
EnQueue(&Q,p);
if(fa == '#')
*T=p;
else
{
s=getHead(Q);//得到队头
while(s->data != fa)
{
DeQueue(&Q);
s=getHead(Q);
}//寻找双亲结点
if(rlflag == 0)
s->lchild = p;
if(rlflag == 1)
s->rchild = p;
}
scanf("%c%c%d",&fa,&ch,&rlflag);
getchar();
}
}
二、二叉树的遍历
1、递归遍历——前序、中序、后序遍历
//前序
void pre_order(BiTree T)
{
if(T)
{
printf("%c ",T->data);
pre_order(T->lchild);
pre_order(T->rchild);
}
}
//中序
void in_order(BiTree T)
{
if(T)
{
in_order(T->lchild);
printf("%c ",T->data);
in_order(T->rchild);
}
}
//后序
void post_order(BiTree T)
{
if(T)
{
post_order(T->lchild);
post_order(T->rchild);
printf("%c ",T->data);
}
}
2、任务分析法遍历
栈中数据域的数据类型,这里的栈没设top指针,不过效果一样。
typedef struct
{
BiNode* tptr;
int task;
}E;
typedef struct stacknode
{
E data;
struct stacknode *next;
}SNode,*Stack;
任务分析法,栈中的数据域包括指向树结点的指针和任务(int类型),task=0表示访问,task=1表示遍历。先初始化task=1,然后把根节点压栈,当栈不为空时,弹栈相当于任务处理,如果task=0就访问,否则就进行遍历,例如先序遍历,那么最先压栈的应该是右子树,此时给它布置的还是遍历任务,然后再压栈左子树,最后压栈子树根,并给它布置访问任务,这样,下一轮处理时就可以访问根结点,然后左子树出栈,给它布置访问任务,以此类推。
A0
B1 B0
C1 C1 C0
void preorder_iter(BiTree T)
{
E e;
BiNode *p;
Stack S;
IniteStack(&S);
e.task=1;
e.tptr=T;
Push(&S,e);
while(!isEmpty_stack(S))
{
Pop(&S,&e);
if(e.task == 0)
printf("%c ",e.tptr->data);
else
{
p = e.tptr;
e.tptr = p->rchild;
e.task = 1;
if(e.tptr)
Push(&S,e);
//-------只需要改变这三步的顺序就可以实现中序和后序遍历
e.tptr = p->lchild;
if(e.tptr)
Push(&S,e);
//---------
e.task = 0;
e.tptr = p;
Push(&S,e);
}
}
}
void inorder_iter(BiTree T)
{
E e;
BiNode *p;
Stack S;
IniteStack(&S);
e.task=1;
e.tptr=T;
Push(&S,e);
while(!isEmpty_stack(S))
{
Pop(&S,&e);
if(e.task == 0)
printf("%c ",e.tptr->data);
else
{
p = e.tptr;
e.tptr = p->rchild;
if(e.tptr)
Push(&S,e);
//-------
e.task = 0;
e.tptr = p;
Push(&S,e);
//---------
e.task = 1;
e.tptr = p->lchild;
if(e.tptr)
Push(&S,e);
}
}
}
void postorder_iter(BiTree T)
{
E e;
BiNode *p;
Stack S;
IniteStack(&S);
e.task=1;
e.tptr=T;
Push(&S,e);
while(!isEmpty_stack(S))
{
Pop(&S,&e);
if(e.task == 0)
printf("%c ",e.tptr->data);
else
{
e.task = 0;
Push(&S,e);
//--------
p = e.tptr;
e.tptr = p->rchild;
e.task = 1;
if(e.tptr)
Push(&S,e);
//---------
e.tptr = p->lchild;
if(e.tptr)
Push(&S,e);
}
}
}
3、层序遍历
利用队列,将每一层入队然后出队输出。先将根节点入队,然后进入循环,不断得到队头元素进行输出,然后再将队头元素出队,如果改结点有左孩子,则左孩子入队,如果有右孩子,右孩子也入队,就可以实现一层结点按从左到右的顺序入队,当队列为空时,循环结束。
void layer_order(BiTree T)
{
Queue Q;
BiNode *p;
IniteQueue(&Q);
if(!T)
return ;
EnQueue(&Q,T);
while(!isEmpty(Q))
{
p = getHead(Q);
printf("%c ",p->data);
DeQueue(&Q);
if(p->lchild)
EnQueue(&Q,p->lchild);
if(p->rchild)
EnQueue(&Q,p->rchild);
}
}
三、数量的统计
1、树的深度
先递归左子树统计左子树深度,再递归右子树求得深度,比较出最大的深度再算上根结点,所以返回最大深度+1
int depth(BiTree T)
{
int dep1,dep2;
if(T)
{
dep1 = depth(T->lchild);
dep2 = depth(T->rchild);
return dep1>dep2?dep1+1:dep2+1;
}
return 0;
}
2、叶子结点个数
叶子结点的左右孩子指针都是指向NULL。
先搜索左子树,再搜索右子树。
void leafCount(BiTree T,int *count)
{
if(!T)
return ;
else
{
leafCount(T->lchild,count);
if(!T->lchild && !T->rchild)
{
(*count)++;
}
leafCount(T->rchild,count);
}
}
3、结点数
根节点+左子树结点+右子树结点,例如左子树,通过递归到最后一层,叶子结点的左右指针为空,均返回0,使得最后一层的返回值是1,然后再逐级返回,最终得到了左子树的结点数,可以自己画个栈图思考一下。
int nodeCount(BiTree T)
{
if(!T)
return 0;
return 1+nodeCount(T->lchild)+nodeCount(T->rchild);
}
4、树的宽度
利用队列,和层序遍历相似,将每一层都保存到队列中,然后用width保留最大宽度,每遍历一层,就更新一次width,通过统计队列长度就可以得到这一层的宽度。
int width(BiTree T)
{
int width = 0;
int len = 0,i;
Queue Q;
BiNode *p;
IniteQueue(&Q);
if(!T)
return 0;
EnQueue(&Q,T);
while(!isEmpty(Q))
{
len = getLength(Q);
width = len > width? len:width;
for(i=0;i<len;i++)
{
p = getHead(Q);
DeQueue(&Q);
if(p->lchild)
EnQueue(&Q,p->lchild);
if(p->rchild)
EnQueue(&Q,p->rchild);
}
}
return width;
}
四、凹入法输出
void dispBiTree(BiTree T,char a,int level)
{
int i,j;
if(!T)
return ;
for(i=0;i<level;i++)
putchar(' ');
printf("%c(%c)",T->data,a);
for(j=i;j<30;j++)//这里可以自定义,打印'-'为了好区分,美观。
putchar('-');//但是j的初始值必须要随着level的变化而变化。
printf("\n");
dispBiTree(T->lchild,'L',level+3);//level加多少也可以自定义。
dispBiTree(T->rchild,'R',level+3);
}
int main()
{
BiTree T;
int count = 0;
Create_Edge(&T);
dispBiTree(T,'D',0);
getchar();
getchar();
return 0;
}
凹入法输出效果图如下: