3.树
3.1 二叉树
3.1.1链式结构
3.1.1.1 递归算法
构建二叉树
1.给出某二叉树先序遍历数组A[1...n]和中序遍历数组B[1...n],编写算法建立该二叉树的二叉链表。
(1)根据先序序列确定树的根节点
(2)根据根节点在中序序列中划分出的二叉树的左右子树所包含的结点,然后根据左右子树的结点在先序序列中的次序可以确定子树的根结点,即回到步骤(1)
重复直到每个子树仅有一个节点为止。
Bitree PreInCreat(ElemType pre[],ElemType in[],int L1,int H1,int L2,int H2)
{ //l1和h1为先序的第一和最后一个结点下标,l2,h2为中序的第一和最后一个结点下标
//初始时,l1=l2=1,h1=h2=n
int L_len,R_len;
Bitree root=(BiTNode*)malloc(sizeof(BiTNode)); //建根节点
root->data=pre[L1]; //根节点的数据
for(i=L2;in[i]!=root->data;i++); //查找根所在位置
L_len=i-L2; //左子树的长度(结点个数)
R_len=H2-i; //右子树的长度(结点个数)
if(L_len) //递归建立左子树
root->lchild=PreInCreat(pre,in,L1+1,L1+L_len,L2,L2+L_len-1);
else //左子树为空
root->lchild=NULL;
if(R_len) //递归建立右子树
root->rchild=PreInCreat(pre,in,H1-R_len+1,H1,H2-R_len+1,H2);
else //右子树为空
root->rchild=NULL;
return root; //返回根结点
}
Bitree PreInCreat(ElemType *pre,ElemType *in,int A_len)
{ //A_len 是当前树的长度
int n=A_len;
Bitree *p; int k;
if(n<=0) return NULL;
Bitree root=(BiTNode*)malloc(sizeof(BiTNode));
root->data=*pre;
for(p=in;p<in+n;p++)
if(*p==*pre) break; //找到根节点
k=p-in; //获得左子树的长度
root->lchild=PreInCreat(pre+1,in,k); //建立左子树
root->rchild=PreInCreat(pre+1+k,in+1+k,n-1-k); //建立右子树
}
2.给出某二叉树后序遍历数组A[1...n]和中序遍历数组B[1...n],编写算法建立该二叉树的二叉链表。
Bitree PostInCreat(ElemType post[],ElemType in[],int L1,int H1,int L2,int H2)
{
int L_len,R_len;
BiTree root = (BiTree)malloc(sizeof(BTNode));
root->data = post[H1];
int i;
for(i=L2;in[i]!=post[H2];i++);
L_len = i - L2;
R_len = H2 - i;
if(L_len) //递归建立左子树
{
PostInCreat(post,in,L1,L1+L_len-1,L2,i-1);
}else
root->lchild = NULL;
if(R_len) //递归建立右子树
{
PostInCreat(post,in,H2-R_len-1,H2-1,i+1,H2);
}else
root->rchild = NULL;
return root;
}
3.给出某二叉树层次遍历数组A[1...n]和中序遍历数组B[1...n],编写算法建立该二叉树的二叉链表。
解:按层次遍历,第一个节点为根,据此将中序遍历分为左右两个部分,若左子树不空,则层次遍历的第二个节点为左子树的根,若左子树为空,则第二个节点为右子树的根,对其左右子树也进行相应的分析。
BiTree LevelInCreat(ElemType level[],in[],int L1,int H1,int L2,int H2)
//level,in分别为层次遍历和中序遍历,L1为层次遍历头,H1为尾,L2,H2类似
{
BiTree bt=new(BiNode);
bt->data=level[L1]; //层次遍历中第一个节点为根
for(i=L2;i<=H2;i++)
if(level[L1]==in[i]) break; //找到中序中的根所在位置
if(i==L2) bt->lchild=NULL; //二叉树无左子树则填空
else{
for(int k=L1+1;k<=H1;k++) //找层次遍历中左子树根节点
{ for(int j=L2;j<i;j++) //将左子树的层次遍历存储起来
if(level[k]==in[j])
level1[++ii]=level[k]; //ii初值为-1
bt->lchild=LevelInCreat(level1,in,0,ii,L1,i-1);//生成左子树
}
}
//右子树操作与左子树类似
if(i==H2) bt->rchild=NULL;
else{
for(k=L1+1;k<=H1;k++)
for(j=i+1;j<=H2;j++)
if(level[k]==in[j])
level1[++ii]=level[k]; //ii初值为-1
bt->rchild=LevelInCreat(level1,in,0,ii,i+1,H2);
}
return bt;
}
解2:(此为自己写的较简便版本,牺牲了时间复杂度,但是代码较简便)
思路:层次遍历中的元素依次与中序遍历比较,最先相同的一定是根节点,递归的建立二叉树。
BiTree LICreat(int lev[],int in[],int n)
//简易版本的建立方式,n为中序遍历的长度
{
BiTree bt=(BiTree)malloc(sizeof(BiTree));
if(!n) //若为空则返回NULL
return NULL;
int i=n; //初始化为n是为了下面i==n的判断
for(int* p=lev;i==n;p++)
//判断中序遍历中是否出现了层次遍历中的元素,没有的话i会一直走到尽头,即为n
{
for(i=0;i<n;i++) //若没有相等的,则该循环结束i为n
if(*p==in[i]) break;
}
bt->data=in[i]; //根节点的值
bt->lchild=LICreat(lev,in,i); //递归建立左子树
bt->rchild=LICreat(lev,in+i+1,n-i-1); //递归建立右子树
return bt;
}
4.满二叉树:
(1)给出一个把后序遍历序列转化为先序遍历序列的算法。
(2)给出一个把先序遍历序列转化为后序遍历序列的算法。
解:(1)主要思想是满二叉树的左子树的数量和右子树的数量相同,先序序列的根为其第一个数。
void PostToPre(ElemType post[],pre[],int L1,int H1,int L2,int H2)
//其中L1,H1为post的头尾下标,同理L2,H2为pre的头尾下标
{
int num=0; //记录左或者右子树的结点个数
if(H1>=L1) //在头尾下标正常时递归
{
pre[L2]=post[H1];//后序遍历的根节点为最后一个
num=(H1-L1)/2; //左右子树的节点数相同
PostToPre(post,pre,L1,L1+num-1,L2+1,L2+num); //递归建立左子树
PostToPre(post,pre,L1+num,H1-1,L2+num+1,H2); //递归建立右子树
}
}
解:(2)与上面类似 王道p123 15
5.由正则二叉树的前序序列和后续序列来确定一棵正则二叉树
BiTree PrePostCreate(ElemType pre[],ElemType post[],int L1,int H1,int L2,int H2)
{
BiTree p;
if(L1<=H1)
{
p=(BiTree)malloc(sizeof(BTNode));
p->data = pre[L1];
if(L1==H1)
{
p->lchild = p->rchild = NULL;
}
else
{
int i,L;
for(i=L2;i<=H2;i++)
if(post[i]==pre[L1+1]) //查找左子树的根
break;
L = i-L2+1; //左子树节点数
p->lchild = PrePostCreate(pre,post,L1+1,L1+L-1,L2,i);
p->rchild = PrePostCreate(pre,post,L1+L+1,H1,i+1,h2-1);
}
}
return p;
}
判断相似(相等)
1.判断两个二叉树是否相似
bool similar(BiTree T1,BiTree T2)
{
int LEFT,RIGHT;
if(T1==NULL&&T2==NULL)
return 1;
else if(T1==NULL||T2==NULL)
return 0;
else
{
LEFT = similar(T1->lchild,T2->lchild);
RIGHT= similar(T1->rchild,T2->rchild);
return LEFT&&RIGHT;
}
}
2.判断两个二叉树是否相等(1800 63)
if(p==null&&q==null)
return(1);
else if(!p&&q||p&&!q)
return(0);
else if(p->data!=q->data)
return(0);
else return(Equal(p->lchild,q->lchild)&&Equal(p->rchild,q->rchlid));
基础操作
1.使用递归算法完成以下二叉树操作:
(1)高度
(2)总结点个数
(3)交换左右子树
(4)叶子节点个数
(5)度为2的结点个数
(6)度为1的结点个数(同上类似)
(7)计算每个结点平衡因子
解:(1)
int height(B)
{ int hl,hr;
if(!B)
return 0;
hl=height(B->lchild);
hr=height(B->rchild);
return hl>hr?hl+1:hr+1;
}
解:(2)
int size(B)
{
return (!B)?0:(size(B->lchild)+size(B->rchild)+1);
}
解:(3)使用后序遍历的思想,为空时递归终点倒回
void exchange(B)
{
if(B) //判空(必写,出口)
{
exchange(B->lchild); //先递归交换左子树
exchange(B->rchild); //递归交换右子树
t=B->lchild; //交换根
B->lchild=B->rchild;
B->rchild=t;
}
}
解:(4)采用递归算法 叶子节点个数=左子树的叶子节点个数+右子树的叶子节点个数,出口为叶子节点
int num_leaf(B)
{
if(!B->lchild)
if(!B->rchild)
return 1;
return num_leaf(B->lchild)+num_leaf(B->rchild);
}
解:(5)
int TwoNodes(B)
{
if(!B)
return 0;
else if(B->lchild&&B->rchild)
return TwoNodes(B->lchild)+TwoNodes(B->rchild)+1;
else
return TwoNodes(B->lchild)+TwoNodes(B->rchild);
}
解:(7)
int Height(BiTree bt)//求二叉树bt的深度
{
int hl,hr;
if (bt==null)
return(0);
else
{
hl=Height(bt->lchild);
hr=Height(bt->rchild);
if(hl>hr)
return (hl+1);
else
return(hr+1);
}
}// Height
void Balance(BiTree bt)
//计算二叉树bt各结点的平衡因子
{
if(bt)
{
Balance(bt->lchild); //后序遍历左子树
Balance(bt->rchild); //后序遍历右子树
hl=Height(bt->lchild);
hr=Height(bt->rchild);//求左右子树的高度
bt->bf=hl-hr; //结点的平衡因子bf
}
}
17.复制一个二叉树的算法
BiTree Copy(BiTree B)
{
BiTree T;
if(B==NULL) T=NULL;
else{
T=new(BiNode);
T->data=B->data;
T->lchild=Copy(B->lchild);
T->rchild=Copy(B->rchild);
}
return T;
}
3.1.1.2 非递归算法
先序遍历
1.先序遍历的非递归算法。
解:借助栈,根节点先入栈,之后栈不空循环,访问出栈节点后入右左节点。
void PreOrder(Bitree T)
{
InitStack(S);Bitree p=T; //借助栈
Push(S,p); //根节点入栈
while(!IsEmpty(S)) //栈空时则退出循环
{
Pop(S,p);visit(p); //出栈并访问
if(p->rchild) //若有右孩子,则右孩子进栈
Push(S,p->rchild);
if(p->lchild) //若有左孩子,则左孩子进栈
Push(S,p->lchild);
}
}
2.求二叉树的先序序列遍历的最后一个结点的指针,使用非递归方式,且不用栈
解:从根开始的任何结点,若有右子树,则是右子树最右下的结点,否则为左子树最右下的结点。
BiNode* PreOrder_Last(BiTree B)
{
BiNode *p=B;
while(p)
{//依次序来,有右孩子就向右,没有就向左,为叶子结点则返回
if(p->rchild)
p=p->rchild;
else if(p->lchild)
p=p->lchild;
else
return p;
}
}
中序遍历
1.中序遍历的非递归算法。
解:中序遍历第一个总是最左端的,然后右边走一次再次走到最左端
void InOrder(Bitree T)
{
InitStack(S);Bitree p=T; //借助栈
while(p||!IsEmpty(S)) //栈不空或者p不空的时候循环
{
if(p) //走到最左端
{
Push(S,p);
p=p->lchild;
}
else{ //若为空则出栈(中间节点)并访问,再走右边孩子
Pop(S,p);visit(p);
p=p->rchild;
}
}
}
后序遍历
1.后序遍历的非递归算法。
void PostOrder(Bitree T)
{
InitStack(S);Bitree p=T; //借助栈
Bitree r=NULL; //辅助指针指向最近访问的结点
while(p||!IsEmpty(S)) //栈不空或者p不空的时候循环
{
if(p)
{
Push(S,p);
p=p->lchild;
}
else{
Gettop(S,p); //注意不是pop
if(p->rchild&&p->rchild!=r)
{
p=p->rchild;
push(S,p);
p=p->lchild;
}
else
}
}
}
2.求二叉树的后序遍历的第一个结点的指针。使用非递归方式,且不用栈
解:若有左子树,则为左子树最左下的叶结点,若无左子树,则为右子树上最左下的叶子节点
BiNode* PostOrder_First(BiTree B)
{
BiNode *p=B;
while(p)
{//依次序来,有左孩子就向左,没有就向右,为叶子结点则返回
if(p->lchild)
p=p->lchild;
else if(p->rchild)
p=p->rchild;
else
return p;
}
}
3.在二叉树中查找值为x的结点,试编写算法打印值为x的节点的所有祖先,假设值为x的结点不多于一个。
解:后序遍历到该结点时栈中均为其祖先(或者说路径)
void Search(BiTree bt,ElemType x) //在二叉树bt中,查找值为x的结点,并打印其所有祖先
{
typedef struct
{BiTree t; int tag; }stack;//tag=0表示左子女被访问,tag=1表示右子女被访问
stack s[]; //栈容量足够大
top=0;
while(bt!=null||top>0)
{
while(bt!=null && bt->data!=x) //结点入栈
{
s[++top].t=bt;
s[top].tag=0;
bt=bt->lchild;
} //沿左分枝向下
if(bt->data==x)
{
printf(“所查结点的所有祖先结点的值为:\n”); //找到x
for(i=1;i<=top;i++)
printf(s[i].t->data);
return;
} //输出祖先值后结束
while(top!=0 && s[top].tag==1)
top--; //退栈(空遍历)
if(top!=0)
{
s[top].tag=1;
bt=s[top].t->rchild;
} //沿右分枝向下遍历
}
}
4.求一棵二叉树的p,q的公共节点
#defined maxsize 1000
typedef struct SNode
{
BiTree t;
int tag;
}SNode;
BiTree findCommonNode(BiTree t,BiTree p,BiTree q)
{
SNode s[maxsize],s1[maxsize];
int top,top1,i,j;
top=top1=0;
BiTree bt;
bt = t;
while(bt||top>0)
{
while(bt!=null && bt!=p && bt!=q)
{
s[++top].t=bt;
s[++top].tag=0;
bt = bt->lchild;
}
if(bt==p)
{
for(i=1;i<=top;i++)
s1[i]=s[i];
top1 = top;
}
if(bt==q)
{
for(i=top;i>0;i--)
for(j=top1;j>0;j--)
if(s1[j].t==s[i].t)
{
return s[i].t;
}
}
while(top!=0 && s[top].tag==1) top--;
if(top!=0)
{
s[top].tag=1;
bt=s[top].t->rchild;
}
}
return NULL;
}
层次遍历
1.层次遍历的非递归算法。
void LevelOrder(BiTree T) //二叉树的层次遍历算法
{
InitQueue(Q);
BiTree p;
EnQueue(Q,T); //根节点入队
while(!IsEmpty(Q)) //队列不空则循环
{
DeQueue(Q,p); //出队列
visit(p); //访问节点
if(p->lchild) //有左孩子则入队
EnQueue(p->lchild);
if(p->rchild) //有右孩子则入队
EnQueue(p->rchild);
}
}
2.给出二叉树的自下而上,从右到左的层次遍历算法
解:使用层次遍历,每次出队列时将该节点放入一个栈中,当层次遍历结束时,将所有的栈中数据退栈。
3.非递归算法求二叉链表表示法的二叉树高度。
解:使用层次遍历的算法,last指向当前层最右边的结点,每次出队都与last比较,若两者相等,则层数加1,并让last指向下一层的最右结点。(注,本代码不使用队列的标准操作)
int Btdepth(BiTree T)
{
if(!T)
return 0; //判空
int front=-1,rear=-1;
BiTree Q[Maxsize];//队列初始化,注意队列尾指针指向队尾元素
int last=0,level=0;
Q[++rear]=T; //根节点入队
BiTree p;
while(front<rear) //队不空则循环
{
p=Q[++front]; //队列元素出队,即正在访问的结点
if(p->lchild) //左孩子不空,则入队
Q[++rear]=p->lchild;
if(p->rchild) //右孩子不空,则入队
Q[++rear]=p->rchild;
if(front==last) //代表出队列的结点为最右元素
{
level++; //层数++
last=rear; //将last指向下一层中最右元素
}
}
}
4.对于树中每一个元素值为x的结点,删去以它为根的子树,并释放相应的空间。
解:需要删除该节点的父结点指向其的指针,同时递归的删除该节点为根的树
使用层次遍历算法来找到父结点
void DeleteXTree(BiTree bt) //递归的删除bt为根的树
{
if(bt)
{
DeleteXTree(bt->lchild);
DeleteXTree(bt->rchild);
free(bt);
}
}
void SearchAndDeleteX(BiTree bt,ElemType x) //删除树中所有节点值为x的结点
{
InitQueue(Q); //借助队列
if(bt)
{
if(bt->data==x) //根为x则全部删除
DeleteXTree(bt);
exit(0);
EnQueue(bt); //根节点入队
while(!IsEmpty(Q)) //队不空则循环
{
DeQueue(Q,p); //出队
if(p->lchild) //左孩子非空
{
if(p->lchild->data==x) //左孩子为x
{
DeleteXTree(p->lchild); //递归删除左孩子
p->lchild=NULL; //左孩子指针置空
}
else
EnQueue(Q,p->lchild); //否则左孩子入队
}
//右孩子操作相同
if(p->rchild) //右孩子非空
{
if(p->rchild->data==x) //右孩子为x
{
DeleteXTree(p->rchild); //递归删除右孩子
p->rchild=NULL; //右孩子指针置空
}
else
EnQueue(Q,p->rchild); //否则右孩子入队
}
}
}
}
5.求非空二叉树的宽度(结点最多的一层的结点个数)
typedef struct{
BiTree data[MaxSize];
int level[MaxSize];
int front,rear;
}Qu;
int BTWidth(BiTree b){
BiTree p;
int k,max,i,n;
Qu.front=Qu.rear=-1;
Qu.rear++;
Qu.data[Qu.rear]=b;
Qu.level[Qu.rear]=1;
while(Qu.front<Qu.rear){
Qu.front++;
p=Qu.data[Qu.front];
k=Qu.level[Qu.frnot];
if(p->lchild!=NULL){
Qu.rear++;
Qu.data[Qu.rear]=p->lchild;
Qu.level[Qu.rear]=k+1;
}
if(p->rchild!=NULL){
Qu.rear++;
QU.data[Qu.rear]=p->rchild;
Qu.level[Qu.rear]=K+1;
}
}
max=0,i=0;
k=1;
while(i<=Qu.rear){
n=0;
while(i<=Qu.rear&&Qu.level[i]==k){
n++;
k++;
}
k=Qu.level[i];
if(n>max)
max=n;
}
return max;
}
6.使用非递归算法交换二叉树的左右子树
使用类似层次遍历的方式,交换每个结点的左右子树
(1)使用队列的方式
void exchange(B)
{
BiNode *p,*q;
if(B)
{
EnQueue(Q,B); //根结点入队
while(!QueueEmpty(Q)) //队为空则退出
{
DeQueue(Q,q); //出队一个
if(p->lchild) EnQueue(Q,p->lchild); //若有左子树,则入队
if(p->rchild) EnQueue(Q,p->rchild); //若有右子树,则入队
q=p->rchild; //交换左右子结点
p=q->lchild;
p->lchild=q;
}
}
}
(2)使用栈的方式
类似队列方式,
(1)根节点放入栈
(2)当栈不空时,取出栈顶元素,交换它的左右子树,并把它的左右子树分别入栈
(3)重复(2)直到队列为空
7.判别给定的二叉树是否是完全二叉树的算法。
解:使用层次遍历的方式,借助队列,利用完全二叉树“若某结点无左子树就不该有右子树”的原则判断。
具体的操作为设置标志位,用层次遍历的方式,若层次遍历中已经出现结点孩子指针为空的结点,再次出现时则不为完全二叉树。
int judge_complete_BT(B)
{
BiNode *p=B;
int tag=0; //用来标注前面的结点为空的个数
if(p==NULL) return 0;
EnQueue(Q,p); //根结点入队
while(!QueueEmpty(Q)) //队为空则退出
{
DeQueue(Q,q); //出队一个
if(p->lchild&&!tag) //若前面没有非空的结点并且左孩子存在
EnQueue(Q,p->lchild); //左孩子入队
else if(p->lchild) //若前面已经有空结点
return 0; //返回0
else
tag=1;
//右孩子相同操作
if(p->rchild&&!tag)
EnQueue(Q,p->rchild);
else if(p->rchild)
return 0;
else
tag=1;
}
return 1;
}
8.复制一个二叉树的算法
BiTree Copy(BiTree B) //使用两个队列
{
BiTree T;
BiNode *p=B;
//两个头节点入队列
EnQueue(Q1,p);
EnQueue(Q2,T);
while(!QueueEmpty(Q1)&&!QueueEmpty(Q2))
{
//出队列
p=DeQueue(Q1);
T=DeQueue(Q2);
T=new(BiNode);
T->data=p->data;
//复制左孩子
if(p->lchild)
{
EnQueue(Q1,p->lchild);
EnQueue(Q2,T->lchild);
}
else
T->lchild=NULL;
//复制右孩子
if(p->rchild)
{
EnQueue(Q1,p->rchild);
EnQueue(Q2,T->rchild);
}
else
T->rchild=NULL;
}
}
9.二叉链表指定某一层k上的叶子节点的个数。
解:采用层次遍历的方式
BiTree Count(BiTree bt){
BiTree p=bt,Q[];
int front=0,rear=1,leaf=0;
int last=1,level=1;Q[1]=p;
while(front<=rear){
p=Q[++front];
if(level==k&&!p->lchild&&!p->rchild)
leaf++;
if(p->lchild)
Q[++rear]=p->lchild;
if(p->rchild)
Q[++rear]=p->rchild;
if(front==last){
level++;last=rear;
}
if(level>k) return (leaf);
}
return 0:
}
3.1.2 顺序结构
递归算法
1.已知一棵二叉树按顺序存储结构进行存储,设计一个算法,求编号分别为 i 和 j 的两个结点的最近的公共祖先结点的值。
int Anchester(int i,int j)
{
while(i!=j)
(i>j?i:j)/=2;
return T[i];
}
2.已知一棵高度为K具有n个结点的二叉树,按顺序方式存储:
(1)编写用先根遍历树中每个结点的递归算法;
(2)编写将树中最大序号叶子结点的祖先结点全部打印输出的算法。
int m=2^K–1; //全局变量
void PreOrder(ElemType bt[],i )
//递归遍历以顺序方式存储的二叉树bt, i是根结点下标(初始调用时为1)。
{
if (i<=m) //设虚结点以0表示
{
printf(bt[i]); //访问根结点
if(2*i<=m && bt[2*i]!=0)
PreOrder(bt,2*i); //先序遍历左子树
if(2*i+1<=m && bt[2*i+1]!=0)
PreOrder(bt,2*i+1);// 先序遍历右子树
}
}//结束PreOrder
//二叉树中最大序号的叶子结点,是在顺序存储方式下编号最大的结点
void Ancesstor(ElemType bt[]) //打印最大序号叶子结点的全部祖先
{
c=m;
while(bt[c]==0)
c--; //找最大序号叶子结点,该结点存储时在最后
f=c/2; //c的双亲结点f
while(f!=0) //从结点c的双亲结点直到根结点,路径上所有结点均为祖先结点
{
printf(bt[f]);
f=f/2;
}//逆序输出,最老的祖先最后输出
}
非递归算法
1.设一棵完全二叉树使用顺序存储在数组bt[1..n]中,请写出进行非递归的前序遍历算法。
void PreOrder(ElemType bt,int n)//对以顺序结构存储的完全二叉树bt进行前序遍历
{
int top=0,s[]; //top是栈s的栈顶指针,栈容量足够大
while(i<=n||top>0)
{
while(i<=n)
{
printf(bt[i]); //访问根结点;
if(2*i+1<=n)
s[++top]=2*i+1; //右子女的下标位置进栈
i=2*i;
} //沿左子女向下
if(top>0)
i=s[top--];
} //取出栈顶元素
}
2.已知深度为h的二叉树以一维数组BT(1:2h-1)作为其存储结构。请写一算法,求该二叉树中叶结点的个数.
int Leaves(int h) //求深度为h以顺序结构存储的二叉树的叶子结点数
{
int BT[];
int len=2h-1,count=0; //BT是二叉树结点值一维数组,容量为2h
for (i=1;i<=len;i++) //数组元素从下标1开始存放
if (BT[i]!=0) //假定二叉树结点值是整数,“虚结点”用0填充
if(i*2)>len) count++; //第i个结点没子女,肯定是叶子
else if(BT[2*i]==0 && 2*i+1<=len && BT[2*i+1]==0)
count++; //无左右子女的结点是叶子
return (count)
}
完全二叉树
1.一颗完全二叉树
(1)由顺序存储结构转换为链式存储结构
(2)由链式存储结构转换为顺序存储结构
解(1)
BiTree Creat(ElemType A[],int i,int n) //n为数组的长度,i为结点位置,初始为1
{
BiTree tree;
if(i<=n)
{
tree=new(BiNode);
tree->data=A[i];
//建立左子树
if(2*i>n) //若左子节点为空
tree->lchild=NULL;
else //不为空则递归的建立左子树
tree->lchild=Creat(A,2*i,n);
//建立右子树
if(2*i+1>n) //若右子节点为空
tree->rchild=NULL;
else //不为空则递归的建立右子树
tree->rchild=Creat(A,2*i+1,n);
}
return tree;
}
解:(2)采用递归方式
void Creat(int A[],int i,BiTree B) //A存放转换后的顺序存储结构,i为指定的位置,初始为1
{
if(B)
{
A[i]=B->data;
//如果左孩子结点存在,则递归的建立左孩子
if(B->lchild)
Creat(A,2*i,B->lchild);
//如果右孩子结点存在,则递归的建立右孩子
if(B->rchild)
Creat(A,2*i+1,B->rchild);
}
}
同时可采用非递归方式,使用层次遍历的方式
void Creat(bt,A)
//使用两个队列,队列Q1用来存放节点,队列Q2用来存放下标
{
EnQueue(Q1,bt);
EnQueue(Q2,1);
while(!QueueEmpty(Q1)&&!QueueEmpty(Q2))
{
p=DeQueue(Q1);
i=DeQueue(Q2);
A[i]=p->data;
//如果存在左孩子,则将左孩子的结点和左孩子的下标存入队列中
if(p->lchild)
{
EnQueue(Q1,p->lchild);
EnQueue(Q2,i*2);
}
//如果存在右孩子,则将右孩子的结点和右孩子的下标存入队列中
if(p->rchild)
{
EnQueue(Q1,p->rchild);
EnQueue(Q2,i*2+1);
}
}
}
3.1.3 表达式树
1.假设一个仅包含二元运算符的算术表达式以链表形式存储在二叉树BT中,写出计算该算术表达式值的算法。
typedef struct node
{ElemType data;
float val;
char optr; //只取‘+’, ‘-’, ‘*’,‘/’
}
struct node *lchild,*rchild }BiNode,*BiTree;
float PostEval(BiTree bt) // 以后序遍历算法求以二叉树表示的算术表达式的值
{
float lv,rv;
if(bt!=null)
{
lv=PostEval(bt->lchild); // 求左子树表示的子表达式的值
rv=PostEval(bt->rchild); // 求右子树表示的子表达式的值
switch(bt->optr)
{
case ‘+’: value=lv+rv; break;
case ‘-’: value=lv-rv;break;
case ‘*’: value=lv*rv;break;
case ‘/’: value=lv/rv;
}
}
return(value);
}
2.给出算法将二叉树表示的表达式二叉树按中缀表达式输出,并加上相应的括号。
int Precede(char optr1,optr2)
// 比较运算符级别高低,optr1级别高于optr2时返回1,相等时返回0,低于时返回-1
{
switch(optr1)
{
case‘+’:case‘-’:
if(optr2==‘+’||optr2==‘-’)return(0);
else return(-1);
case‘*’:case‘/’:
if(optr1==‘*’||optr2==‘/’)return(0);
else return(1);
}
}
void InorderExp (BiTree bt)
//输出二叉树表示的算术表达式,设二叉树的数据域是运算符或变量名
{
int bracket;
if(bt)
{
if(bt->lchild!=null)
{
bracket=Precede(bt->data,bt->lchild->data)//比较双亲与左子女运算符优先
if(bracket==1)
printf(‘(’);
InorderExp(bt->lchild); //输出左子女表示的算术表达式
if(bracket==1)
printf(‘)’); //加上右括号
}
printf(bt->data); //输出根结点
if(bt->rchild!=null) //输出右子树表示的算术表达式
{
bracket=Precede(bt->data,bt->rchild->data)
if (bracket==1)
printf(“(”); //右子女级别低,加括号
InorderExp (bt->rchild);
if(bracket==1)printf(“)”);
}
}
}
3.试运用后序遍历二叉树的规则,写出对表达式求值的算法
char ar[maxsize];//maxsize是后缀表达式所能达到的最大长度
int i=1;
void PostOrder(BiTree t )//后序遍历二叉树t,以得到后缀表达式
{
if(t)
{
PostOrder(t->lchild);
PostOrder(b->rchild);
ar[i++]=b->data;
}
}//结束PostOrder
void EXPVALUE()
//对二叉树表示的算术表达式,进行后缀表达式的求值
{
ar[i]=‘\0’; //给后缀表达式加上结束标记
char value[]; //存放操作数及部分运算结果
i=1; ch=ar[i++];
while(ch!=‘\0’)
{
switch(ch)
{
case ch in op:
opnd1=pop(value);opnd2=pop(value); //处理运算符
push(operate(opnd2,ch,opnd1));break;
default:
push(value,ch); //处理操作数,压入栈中
}
ch=ar[i++]; //读入后缀表达式
}
printf(value[1]); //栈中只剩下一个操作数,即运算结束。
} //结束EXPVALUE
3.1.4 二叉树遍历杂项
1.利用叶子节点中的空指针域将所有叶子节点链接为一个带头结点的双链表。(1800算法57)
2.将二叉树的叶结点按从左到右的顺序连成一个单链表。
LinkedList head,pre=null;
LinkedList InOrder(BiTree bt){
if(bt){
InOrder(bt->lchild);
if(bt->lchild==NULL&&bt->rchild==NULL)
if(pre==NULL){
head=bt;
pre=bt;
}
else{
pre->rchild=bt;
pre=bt;
}
InOrder(bt->rchild);
pre->rchild=NULL;
}
return head;
}
4.所有分支节点度数为2的二叉树称为正则树,写一个算法判断一颗树是否是正则树。(递归)
解:采用任何递归遍历算法,将访问节点的操作改为判断度是否为1,为1则非二叉树。
3.2 线索二叉树
1。线索树的插入问题(1800 算法75,76,77)
9.构造前序线索二叉树
void preThread(BiTree p,BiTree &pre)
{
if(p!=NULL)
{
if(p->lchild==NULL)
{
p->lchild = pre;
p->ltag = 1;
}
if(pre!=NULL && pre->rchild==NULL)
{
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
if(p->ltag!=1)
preThread(p->lchild,pre);
if(p->rtag!=1)
preThread(p->rchild,pre);
}
}
10.构造中序线索二叉树
void inThread(BiTree p,Bitree &pre)
{
if(p!=NULL)
{
inThread(p->lchild,pre);
if(p->lchild==NULL) //构造左线索
{
p->lchild = pre;
p->ltag = 1;
}
if(pre!=NULL&&pre->rtag==0) //构造右线索
{
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
inThread(p->rchild);
}
}
11.构造后序线索二叉树
void postThread(BiTree p,Bitree &pre)
{
if(p!=NULL)
{
postThread(p->lchild,pre);
postThread(p->rchild,pre);
if(p->lchild==NULL) //构造左线索
{
p->lchild = pre;
p->ltag = 1;
}
if(pre!=NULL && pre->rtag==0) //构造右线索
{
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
}
}
12.前序线索二叉树的遍历
void preOrder(BiTree root)
{
if(root!=NULL)
{
BiTree p = root;
while(p!=NULL)
{
while(p->ltag==0) //遇到左孩子不存在的节点则停止
{
visit(p);
p = p->lchild;
}
visit(p); //访问这个左孩子不存在的节点
p = p->rchild; //转向它的后继 继续访问
}
}
}
13.中序线索二叉树的遍历
不带头结点
ThreadNode *Firstnode(ThreadNode *p) //中序线索二叉树中中序序列下的第一个结点
{
while(p->ltag==0)
p=p->lchild; //最左下结点
return p;
}
ThreadNode *Nextnode(ThreadNode *p) //后继结点
{
if(p->rtag==0) //无线索,则遍历右子树
return Firstnode(p->rchild);
else //有线索,则返回线索
return p->rchild;
}
void Inorder(ThreadNode *T) //遍历中序线索二叉树
{
for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p))
visit(p);
}
void Inorder(ThreadNode *T)
{
ThreadNode *p=T; //指向正式序列的第一个值
while(p) //若最后指向NULL则退出
{
while(p->ltag==0) //访问中序下第一个结点
p=p->lchild;
visit(p);
while(p->rtag==1) //有线索则访问下一个,注意最后一个右孩子为NULL
{
p=p->rchild;
visit(p);
}
p=p->rchild; //走到无线索,向右走
}
}
带头结点
void Inorder(ThreadNode *T)
{
ThreadNode *p=T->lchild; //指向正式序列的第一个值
while(p!=T) //若最后指向头结点则退出
{
while(p->ltag==0) //访问中序下第一个结点
p=p->lchild;
visit(p);
while(p->rtag==1&&p->rchild!=T) //有线索则访问下一个,注意最后一个线索指向头结点
{
p=p->rchild;
visit(p);
}
p=p->rchild; //走到无线索,向右走
}
}
14.后序线索二叉树的遍历
15.写出在中序线索二叉树里查找指定结点在后序的前驱结点的算法。
BiTree findNode(BiTree T,BiTree bt)
{
if(bt->rtag==0) //存在右子树时
return bt->rchild;
else if(bt->ltag==0) //不存在右子树 但有左子树
return bt->lchild;
else if(bt->lchild==NULL) //为中序第一节点 所以无后序前驱结点
return NULL;
else //其他情况
{
while(bt->ltag&&bt->lchild!=NULL)
bt = bt->lchild;
if(bt->ltag==0)
return bt->lchild;
else //此种情况为单枝树,bt为叶子,所以无后序前驱结点
return NULL;
}
}
中序线索二叉树里查找指定结点在前序下的后继结点的算法
BiTree treNext(BiTree t)
{
BiTree p;
if(!t->ltag)
p = t->lchild;
else if(!t->rtag)
p = t->rchild;
else
{
p = t;
while(p&&p->rtag)
p = p->rchild;
if(p)
p = p->rchild;
}
return p;
}
中序线索二叉树里中序下的最后一个节点
BiTree inLast(BiTree t)
{
BiTree p = t;
while(p && !p->rtag)
p = p->rchild;
return p;
}
中序下的前驱
BiTree inPrior(BiTree t)
{
BiTree p=t->lchild;
while(p&&!p->rtag)
p = p->rtag;
return p;
}
附
前序线索二叉树的前驱后继
中序线索二叉树的前驱后继
后序线索二叉树的给定结点的前驱后继,给定结点node,前驱或者后继储存到x
void Post_prior(ThreadNode *node,ThreadNode *x) //后序线索树前继结点
{
if(node) //判断空
if(node->rflag==0) //如果有右孩子,则右孩子一定为前驱
x=node->rchild;
else
x=node->left; //左孩子若存在,则没右孩子的情况下返回左孩子,左孩子若不存在则返回前驱线索
}
void Post_next(ThreadNode *bt,ThreadNode *node,ThreadNode *x) //bt为根,后序线索树后继结点
{
x=bt; //赋值x为根
if(node!=bt&&node!=NULL)
{
if(node->rflag) //若有后序线索,则得到后序线索
x=node->rchild;
else{
//没有后序线索的情况下
do{
t=x; //从根节点(后序序列最后一个结点)
Post_prior(t,x); //逐渐找前驱
}while(x!=node); //若该节点的前驱为node
x=t; //该节点为后继
}
}
}
3.2 树
1.已知一棵树的层次序列以及每个节点的度,编写算法构造树的孩子-兄弟链表。
#define maxsize 15
void createCSTree_Degree(CSTree &T,DataType e[],int degree[],int n)
{
CSNode *pointer[maxsize];
int i,j,d,k=0;
for(i=0;i<n;i++)
{
pointer[i] = (CSTree)malloc(sizeof(CSNode));
pointer[i]->data = e[i];
pointer[i]->lchild = pointer[i]->rsilbling = NULL;
}
for(i=0;i<n;i++)
{
d = degree[i];
if(d)
{
k++;
pointer[i]->lchild = pointer[k];
for(j=2;j<=d;j++)
pointer[k]->lchild = pointer[k++];
}
}
T = pointer[0];
free(pointer);
}
2.以孩子兄弟链表为存储结构,请设计递归和非递归的算法求树的深度。(1800,算法15)
由孩子兄弟链表表示的树高度的递归模型是:若树为空,高度为零;若第一个子女为空,高度为1和兄弟子树高度的大着;否则,高度为第一子女树高度加1和兄弟子树高度的大者。递归算法的核心算法如下:
if(bt==null) return(0);
else if(!bt->firstchild)
return(max(1,height(bt->nextsibling)));
else{
hc=height(bt->firstchild); //第一子女树高
hs=height(bt->nextsibling); //兄弟树高
if(hc+1>hs)
return(hc+1);
esle return(hs);
}
其非递归算法使用队列、逐层遍历树,取得树的高度。
Q[rear]=t; //Q是以树中结点为元素的队列
while(ftont<=last){ //初始front=rear=1
t=Q[front++]; //队头出列
while(t!=null) //层次遍历
{
if(t->firstchild)
Q[++rear]=t->firstchild; //第一个子女入队
t=t->nextsibling; //同层兄弟指针后移
}
if(front>last){
h++;
last=rear;
}
}
3.编写递归程序求以孩子兄弟链表结构表示的树的叶子节点个数。
int count(CSTree){
if(t==null)
return(0);
else if(t->firstchild==null)
return(1+count(t->nextsibling));
else return(count(t->firstchild)+count(t->nextsibling));
}
4.编写程序计算以孩子兄弟链表表示的树T的度。(1800算法73)
3.3 哈夫曼树
1.求出二叉树的带权路径长度(WPL)
static int wpl = 0; //全局变量来保存wpl
int Pre_Order(BiTree root,int deep)
{
if(root->lchild==NULL && root->rchild==NULL) //为叶节点的时候则累加
wpl+=deep*root->weight;
if(root->lchild!=NULL) //左子树不空 则对左子树递归
Pre_Order(root->lchild,deep+1);
if(root->rchild!=NULL)
Pre_Order(root->rchild,deep+1); //右子树不空 则对右子树递归
return wpl;
}
int wpl(BiTree root)
{
Pre_Order(root,0);
return wpl;
}