通过上机实践,帮助学生进一步掌握指针变量和动态变量的含义,掌握二叉树的结构特性,以及各种存储结构的特点及适用范围,掌握用指针类型描述、访问和处理二叉树的运算。
【实验内容】
实验题目一:层序遍历二叉树
【实验步骤】
1 已知二叉树以二叉链表作为存储结构,写一个算法按层序遍历它,通过程序在终端屏幕上打印出它的层序序列。
2 先建立二叉树的二叉链表存储结构,再遍历它。
3 利用队列完成算法。
编程实现以二叉链表存储的二叉树的中序遍历的非递归算法。
编程实现二叉树的中序线索链表的生成(线索化)及其遍历的算法。
实验题目二:交换二叉树的所有结点的左右子树
【实验步骤】
1 已知二叉树以二叉链表作为存储结构,写一个算法交换该二叉树的所有结点的左右子树。
2 先建立二叉树的二叉链表存储结构,再完成算法,注意结果的输出形式!
3 利用栈。可设二叉树的根指针为t,且以二叉链表表示,可利用一个指针栈来实现,且设栈单元包括数据域和指针域,当树非空时,将当前的树根结点进栈,同时将当前栈顶元素出栈当作根结点,然后依据当前的根结点是否具有孩子结点来判定是否将其左右指针交换;再将交换后的左指针或右指针入栈,如此反复,直到栈空。
【程序说明】
该程序集成了如下功能:
(1) 二叉树的建立
(2) 递归和非递归先序,中序和后序遍历二叉树
(3) 按层次遍历二叉树
(4) 交换二叉树的左右子树
(5) 输出叶子结点
(6) 递归和非递归计算叶子结点的数目
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <conio.h>
#include <math.h>
typedef struct Binnode{
char data;
struct Binnode *lchild;
struct Binnode *rchild;
}Binnode;
/*按照前序遍历建立二叉树*/
void Creat_Bintree(Binnode **t)
{
char ch;
scanf("/n%c",&ch);
if(ch=='0') *t=NULL;
else
{
*t=(Binnode*)malloc(sizeof(Binnode));
if(!*t)exit(OVERFLOW);
(*t)->data=ch;
printf("%c: left",ch); Creat_Bintree(&(*t)->lchild);
printf("%c: right",ch);Creat_Bintree(&(*t)->rchild);
}
}
/*按照前序递归遍历二叉树*/
void Preorder1(Binnode *t)
{
if(t!=NULL)
{
printf("%c",t->data);
Preorder1(t->lchild);
Preorder1(t->rchild);
}
}
/*按照中序递归遍历二叉树*/
void Inorder1(Binnode *t)
{ /* printf("/n输出中序递归遍历序列:");*/
if(t!=NULL)
{
Inorder1(t->lchild);
printf("%c",t->data);
Inorder1(t->rchild);
}
}
/* 按照后序递归遍历二叉树*/
void Posorder1(Binnode *t)
{ /* printf("/n输出后序递归遍历序列:");*/
if(t!=NULL)
{
Posorder1(t->lchild);
Posorder1(t->rchild);
printf("%c",t->data);
}
}
/*按照前序非递归遍历二叉树*/
void preorder2(Binnode *root)/*先序非递归遍历二叉树*/
{ Binnode *p,*stack[100];
int top ;
p=root;
if(root!=NULL)
{top=1;
stack[top]=p;
while(top>0)
{
p=stack[top] ;/*将右小孩放入栈*/
top--;
printf("%c",p->data);
if(p->rchild!=NULL)
{top++;
stack[top]=p->rchild;
}
if(p->lchild!=NULL)
{top++;
stack[top]=p->lchild;
}
}
}
}
/*按照中序非递归遍历二叉树*/
void Inorder2(Binnode *root)/*中序非递归遍历二叉树*/
{ Binnode *p,*stack[100];
int top=0;
p=root;
do{
while(p!=NULL)
{
top++;
stack[top]=p;
p=p->lchild;
}
if(top>0)
{
p=stack[top];/*p所指的节点为无左子树或其左子树已经遍历过*/
top--;
printf("%c",p->data);
p=p->rchild;
}
}while(p!=NULL||top!=0);
}
/*按照后序非递归遍历二叉树*/
void Posorder2(Binnode *t)
{
Binnode *s[100];
int top=-1;
int flag[100];
while(t!=NULL||top!=-1)
{
while(t)
{
top++;
flag[top]=0;
s[top]=t;
t=t->lchild;
}
while(top>=0&&flag[top]==1)
{
t=s[top];
printf("%c",t->data);
top--;
}
if(top>=0)
{
t=s[top];
flag[top]=1;
t=t->rchild;
}
else
{
t=NULL;
}
}
}
/*按照层次遍历二叉树*/
int front=0,rear=1;
void Levelorder(Binnode *t)
{
Binnode *q[100];
q[0]=t;
/* int front=0,rear=1;*/
while(front<rear)
{
if(q[front])
{
printf("%c",q[front]->data);
q[rear++]=q[front]->lchild;
q[rear++]=q[front]->rchild;
front++;
}
else
{
front++;
}
}
}
/*递归法将二叉树的左右子树互换*/
void Exchange1(Binnode *t)
{
Binnode *temp;
if(t)
{
Exchange1(t->lchild);
Exchange1(t->rchild);
temp=t->lchild;
t->lchild=t->rchild;
t->rchild=temp;
}
}
/*非递归法将二叉树的左右子树互换*/
void Exchange2(Binnode *t)
{
Binnode *temp;
Binnode *s[100];
int top=0;
while(t||top)
{
if(t)
{
s[top++]=t;
temp=t->lchild;
t->lchild=t->rchild;
t->rchild=temp;
t=t->lchild;
}
else
{
t=s[--top]->rchild;
}
}
}
/*递归法求叶子结点个数*/
int Leaves_Num1(Binnode *t)
{
if(t)
{
if(t->lchild==NULL&&t->rchild==NULL)
{
return 1;
}
return (Leaves_Num1(t->lchild)+Leaves_Num1(t->rchild));
}
else return 0;
}
/*非递归法求叶子结点个数*/
int Leaves_Num2(Binnode *t)
{
Binnode *s[100];
int count=0,top=0;
while(t||top>0)
{
if(t)
{
s[top++]=t;
if(t->lchild==NULL&&t->rchild==NULL)
{
count++;
printf("%c",t->data);/*输出叶子结点*/
}
t=t->lchild;
}
else
{
t=s[--top]->rchild;
}
}
return count;
}
int main()
{
Binnode *proot=NULL;
int p,q;
printf("1.CreateBitree:/n");
Creat_Bintree(&proot); /*建立二叉树时,无左右孩子的结点输入’0’值*/
printf("/n2.Output Digui PreOrder:/n");
Preorder1(proot);
printf("/n3.Output Not Digui PreOrder:/n");
preorder2(proot);
printf("/n4.Output Digui InOrder:/n");
Inorder1(proot);
printf("/n5.Output Not Digui InOrder:/n");
Inorder2(proot);
printf("/n6.Output Digui PostOrder:/n");
Posorder1(proot);
printf("/n7.Output Not Digui PostOrder:/n");
Posorder2(proot);
printf("/n8.Output LevelOrde:/n");
Levelorder(proot);
Exchange1(proot);
printf("/n9.Output Digui Exchange:/n");
Preorder1(proot);
Exchange2(proot);
printf("/n10.Output Not Digui Exchange:/n");
Preorder1(proot);
printf("/n11.Leaves_Node:/n");
q=Leaves_Num2(proot);
printf("/n12.Output Not Digui Leaves_Num:%d/n",q);
p=Leaves_Num1(proot);
printf("13.Output Digui Leaves_Num:%d/n",p);
printf("/nThis System Once Again/n");
return 0;
}
【思考题】
编程实现二叉树的中序线索链表的生成(线索化)及其遍历的算法。
【调试说明】
(1)建立二叉树时,必须按先序遍历方式依次输入结点值(该程序中为字符)。当输入叶子结点时,它的左右子树为空以输入“0”值作为结束。
(2)该程序集中了二叉树操作的常用算法,根据需要可以在此基础上进行裁剪,只实现其中一种即可,并调试运行。
当用二叉链表作为二叉树的存储结构时,因为每个结点中只有指向其左、右孩子结点的指针,所以从任一结点出发只能直接找到该结点的左、右孩子。在一般情况下靠它无法直接找到该结点在某种遍历次序下的前驱和后继结点。如果在每个结点中增加指向其前驱和后继结点的指针,将降低存储空间的效率。
与此同时,我们可以证明:在n个结点的二叉链表中含有n+1个空指针。因为含n个结点的二叉链表中含有2n个指针,除了根结点,每个结点都有一个从父结点指向该结点的指针,因此一共使用了n-1个指针,所以在n个结点的二叉链表中含有2n-(n-1)=n+1个空指针。
因此,可以利用这些空指针,存放指向结点在某种遍历次序下的前驱和后继结点的指针。这种附加的指针称为线索,加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。为了区分一个结点的指针是指向其孩子的指针,还是指向其前驱或后继结点的线索,可在每个结点中增加两个线索标志。这样,线索二叉树结点类型定义为:
Lchild |
Ltag |
Data |
Rtag |
Rchild |
其中:
1. Ltag=0时,表示Lchild指向该结点的左孩子;
2. Ltag=1时,表示Lchild指向该结点的线性前驱结点;
3. Rtag=0时,表示Rchild指向该结点的右孩子;
4. Rtag=1时,表示Rchild指向该结点的线性后继结点;
以二叉链表结点数据结构所构成的二叉链表作为二叉树的存储结构,叫做线索二叉链表;指向结点的线性前驱或者线性后继结点的指针叫做线索;加上线索的二叉树称为线索二叉树;对二叉树以某种次序遍历将其变为线索二叉树的过程叫做线索化。
中序线索化是指用二叉链表结点数据结构建立二叉树的二叉链表,然后按照中序遍历的方法访问结点时建立线索。
例如有如上图所示二叉树,则中序遍历的顺序是:O / J * I + H A G
【参考程序】
#include <stdio.h>
#include <malloc.h>
typedef enum{Link,Thread} PointerTag; /*指针标志*/
typedef char DataType;
typedef struct BiThreTree{ /*定义结点元素*/
PointerTag LTag,RTag;
DataType data;
struct BiThreTree *lchild,*rchild;
}BiThreTree;
BiThreTree *pre; /*全局变量,用于二叉树的线索化*/
BiThreTree *CreateTree() /*按前序输入建立二叉树*/
{
BiThreTree *T;
DataType ch;
scanf("%c",&ch);
if(ch==’#’)
T=NULL;
else
{T=(BiThreTree *)malloc(sizeof(BiThreTree));
T->data=ch;
T->LTag=Link; /*初始化时指针标志均为Link*/
T->RTag=Link;
T->lchild=CreateTree();
T->rchild=CreateTree();
}
return T;
}
void InThread(BiThreTree *T)
{
BiThreTree *p;
p=T;
if(p)
{
InThread(p->lchild);
if(!p->lchild)
{ p->LTag=Thread;
p->lchild=pre;
}
if(!pre->rchild)
{ pre->RTag=Thread;
pre->rchild=p;
}
pre=p;
InThread(p->rchild);
}
}
BiThreTree *InOrderThrTree(BiThreTree *T) /*中序线索化二叉树*/
{
BiThreTree *Thre; /*Thre为头结点的指针*/
Thre=(BiThreTree *)malloc(sizeof(BiThreTree));
Thre->lchild=T;
Thre->rchild=Thre;
pre=Thre;
InThread(T);
pre->RTag=Thread;
pre->rchild=Thre;
Thre->rchild=pre;
return Thre;
}
void InThrTravel(BiThreTree *Thre) /*中序遍历二叉树*/
{
BiThreTree *p;
p=Thre->lchild;
while(p!=Thre) /*指针回指向头结点时结束*/
{
while(p->LTag==Link)
p=p->lchild;
printf("%4c",p->data);
while(p->RTag==Thread&&p->rchild!=Thre)
{p=p->rchild;
printf("%4c",p->data);
}
p=p->rchild;
}
}
void main()
{
BiThreTree *T,*Thre;
printf(“PreOrder Create Binary Tree:/n”);
T=CreateTree();
Thre=InOrderThrTree(T);
printf(“InOrder Traverse Binary Tree:/n”);
InThrTravel(Thre);
system("pause");
}
【调试举例】
(1)建立二叉树时,按先序遍历方式输入:“ABD##EH##I##CF##G##”,其中“#”表示空指针。
(2)建立的二叉树为: A
B C
D E F G
H I
(3)程序输出为中序遍历线索化二叉树的结果:DBHEIAFCG
已知A、B和C为三个递增有序的线性表,现要求对A表作如下操作:删除那些既在B表中出现又在C表中出现的元素。
【实验步骤】
1.试对顺序表编写实现上述操作的算法并上机编写代码,要求算法尽可能高效。在实验报告中分析你的算法的时间复杂度。
2.A表中要删除的元素实际上就是在三个表中都存在的元素。注意这三个线性表都是递增有序的线性表!可以利用这一性质减少对线性表“扫描”的趟数。
3.改用单链表作为存储结构编写算法,请释放A表中无用的结点空间,并上机编程实现。
【算法思想】
先从B和C中找出共有元素same,再从A中从当前位置开始,凡小于same的元素均保留,等于same的就跳过,大于same时就再找下一个same。
【顺序存储结构算法描述】
void SqList_Delete(SqList&A, SqList B, SqList C)
{
i=0;j=0;m=0;/*i指示A中元素原来的位置,m为移动后的位置*/
while(i<A.length&&j<B.length&&k<C.length)
{
if(B.elem[j]<C.elem[k]) j++;
else if(B.elem[j]>C.elem[k])k++;
else{
same= B.elem[j];/*找到了相同元素same*/
while(B.elem[j]==same)j++;
while(C.elem[k])==same)k++;/*j,k后移到新的元素*/
while(i<A.length&&A.elem[i]<same)
A.elem[m++]=A.elem[i++];/*需要保留的元素移动到新位置*/
while( i<A.length&&A.elem[i]==same)i++;
}/*else*/
}/*while*/
while(i<A.length)
A.elem[m++]=A.elem[i++];/*A的剩余元素重新存储*/
A.length=m;
}/*SqList_Delete*/
【顺序存储结构参考程序】
#include<stdio.h>
main()
{
#define N 3/*宏定义,代表数组维数*/
#define M 4
#define T 5
int i,j,k,r,m,same;/*定义变量分别指向数组a,b,c*/
int a[N],b[M],c[T];
printf(“please input numbers:/n”);
for (i=0;i<N;i++) scanf("%d",&a[i]);
for (j=0;j<M;j++) scanf("%d",&b[j]);
for (k=0;k<T;k++) scanf("%d",&c[k]);
i=0;j=0;m=0;k=0;/*分别赋值为0,即指向数组的第一元素*/
while(i<N&&j<M&&k<T)
{
if(b[j]<c[k])j++;
else if(b[j]>c[k]) k++;
else{
same=b[j];
while(b[j]==same)j++;
while(c[k]==same)k++;
while(i<N&&a[i]<same)
a[m++]=a[i++];
while(i<N&&a[i]==same) i++;
}
}
while(i<N)
a[m++]=a[i++];
printf("a[%d]={",m);/*输出删除元素后的数组a/*
for (r=0;r<m-1;r++)printf("%d,",a[r]);
printf("%d",a[m-1]);
printf("}/n");
return;
}
【程序调试】
程序运行是屏幕显示:please input numbers: 此时输入三组测试数据,数据间用空格隔开:
2 3 4回车
3 4 5 6 回车
4 5 6 7 8 回车
程序输出结果:a[3]={2,3},即删除了数组a中即在b和c中都出现的元素。
【单链表存储结构参考程序】
# include <stdio.h>
# include <stdlib.h>
Typedef struct LNode{
int data;
struct LNode *next;
}Lnode,*LinkList;/*定义链表节点的结构*/
void main ( )
{/*功能:建立单链表A,B,C,并且删除A中均在B和C中出现的数据。*/
CreateList (LinkList head);/*函数声明*/
ListDelete (LinkList La, LinkList Lb ,LinkList Lc);
Print (LinkList head);
LinkList headA,headB,headC;
headA=NULL;
headB=NULL;
headC=NULL;
headA=CreateList(headA);/*建立链表*/
headB=CreateList(headB);
headC=CreateList(headC);
print(headA);/*输出显示链表数据*/
print(headB);
print(headC);
headA=ListDelete(headA,headB,headC);/*删除A中满足条件的节点*/
Print (headA);
}
LinkList createList(LinkList head)
{/*功能:建立有头节点head的数据单链表*/
LinkList p,q;
p=q=(LinkList)malloc(sizeof(Lnode));
printf("/ninput data:/n");
scanf("%d",&p->data);
p->next=NULL;
while(p->data>0)/*建立链表的输入数据必须大于0,数据输入时以输入任何负数作为结束*/
{
if(head==NULL) head=p;
else{
q->next=p;
q=p;}
p=(LinkList)malloc(sizeof(Lnode));
printf("/ninput data:/n");
scanf("%d",&p->data);
p->next=NULL;
}
return head;
}
LinkList ListDelete(LinkList La,LinkList Lb,LinkList Lc)
{/*功能:删除A中的有关节点*/
LinkList pa,pb,pc,qa;
pa=La;
qa=La;
pb=Lb;
pc=Lc;
while(pb&&pc)
{
if(pb->data<pc->data)
pb=pb->next;
else if(pb->data>pc->data)
pc=pc->next;
else/*首先找到B和C中都存在的数据,并且定位*/
{
if(pa->data<pb->data)/*在A中找到B和C中都存在的数据*/
{ qa=pa;
pa=pa->next;
}
else if(pa->data==pb->data)/*删除节点*/
{ qa->next=pa->next;
f ree(pa);
pa=qa->next;
}
else pb=pb->next;
}/*else*/
}/*while*/
return La;
}
void print(LinkList head)
{/*输出链表数据*/
LinkList r;
r=head;
printf("/noutput data:/n");
while(r!=NULL)
{
printf("%d ",r->data);
r=r->next;
}
return;
}
【实验分析】
(1)因为单链表A,B,C中的元素都是递增有序的,所以先通过遍历B和C找到它们共同的元素,然后再查找该元素是否也存在于A中,存在则删除;否则再查找B和C中的下一个相同元素。
(2)无论是顺序存储结构还是单链表存储结构,都采用一次遍历的方式来找到即在B和C中都出现的元素,故时间复杂度为O(n)。