数据结构实验2

一、目的和要求:
1. 熟练掌握二叉树的定义、性质和存储结构;
2,熟练掌握二叉树的三种遍历和线索化以及遍历算法的各种描述形式;
3.学会编写实现树的各种操作的算法;
4.掌握树的定义、存储结构与二叉树的转换、遍历;
5.掌握哈夫曼树的建立和实现哈夫曼编码;
二、实验题目:
1.二叉树的建立与遍历:掌握建立二叉树的方法,实现先序、中序、后序三种遍历算法。
问题描述: 构造一棵不少于8个结点的二叉树,并分别输出其先序遍历、中序遍历和后序遍历的结果。
问题分析:二叉树先序遍历可利用非递归算法实现,首先使用一个栈stack,将根结点入栈,开始循环:从栈中退出当前结点p;先访问它,然后将其它右结点入栈,再将其左结点入栈,如此直到栈空为止。后序遍历也可使用非递归算法,根据后序遍历的二叉树的递归定义,转换成非递归函数时采用一个栈保存返回的结点,先遍历根结点的所有左结点并入栈,出栈一个结点,然后遍历该结点的右结点并入栈,再遍历该右结点的所有左结点并入栈,当一个结点的左右子树均访问后再访问该结点,如此,直到栈空。二叉树的中序遍历在此是用递归算法实现的(以达到递归和非递归算法都能运用的目的),先遍历左子树,然后访问根结点,最后遍历右子树,如此循环,直至访问完所有的结点。
2.树和二叉树的应用:掌握建立哈夫曼树的方法,实现哈夫曼编码。
问题描述:对任意输入的一段英文,为每个字符编制其相应的赫夫曼编码;并利用该编码为任意输入的0、1序列进行解码.
(1)初始化: 从终端读入一段英文字符,统计每个字符出现的频率,建立赫夫曼树,并将该树存入某文件;
(2)编码: 利用建好的赫夫曼树对各字符进行编码,用列表的形式显示在屏幕上,并将编码结果存入另一文件中;
(3)解码:  利用保存的赫夫曼编码,对任意输入的0,1序列能正确解码。
三、实验步骤与源程序:
1.二叉树的遍历步骤:
1).定义二叉树的类型;
2) .构造头节点、左孩子、右孩子;
3).中序遍历;
4).先序遍历;
5).后序遍历
其源程序如下:
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
struct node 
{
int data;
struct node *right, *left;
}*root,*p,*q;   /*定义二叉树的类型*/
struct node *make(int y)  /*构造头节点*/
{
struct node *newnode;
newnode=(struct node *)malloc(sizeof(struct node));
newnode->data=y;
newnode->right=newnode->left=NULL;
return(newnode);
}

void left(struct node *r,int x)  /*构造左孩子*/
{
if(r->left!=NULL)
printf("\n Invalid !");
else
r->left=make(x);
}

void right(struct node *r,int x)  /*构造右孩子*/

{
if(r->right!=NULL)
printf("\n Invalid !");
else
r->right=make(x);
}

void inorder(struct node *r)   /*中序遍历算法*/
{
if(r!=NULL)
{
inorder(r->left);
printf("\t %d",r->data);
inorder(r->right);
}
}

void preorder(struct node *r)    /*先序遍历算法*/
{
if(r!=NULL)
{
printf("\t %d",r->data);
preorder(r->left);
preorder(r->right);
}
}

void postorder(struct node *r)    /*后序遍历算法*/
{
if(r!=NULL)
{
postorder(r->left);
postorder(r->right);
printf("\t %d",r->data);
}
}

void main()          /*main主函数*/
{
int no,choice;
printf("\n Enter the root:");
scanf("%d",& no);
root=make(no);
p=root;
while(1)
{
printf("\n Enter another number:");
scanf("%d",& no);
if(no==-1)
break;
p=root;
q=root;
while(no!=p->data && q!=NULL)
{
p=q;
if(no<p->data)
q=p->left;
else
q=p->right;
}
if(no<p->data)
{
printf("\n Left branch of %d is %d",p->data,no);
left(p,no);
}
else
{
right(p,no);
printf("\n Right Branch of %d is %d",p->data,no);
}
}
while(1)
{
printf("\n 1.Inorder Traversal \n 2.Preorder Traversal \n 3.Postorder Traversal \n 4.Exit");
printf("\n Enter choice:");
scanf("%d",&choice);
switch(choice)
{
case 1 :inorder(root);
break;
case 2 :preorder(root);
break;
case 3 :postorder(root);
break;
case 4 :exit(0);
default:printf("Error ! Invalid Choice ");
break;
}
getch();
}
}
运行结果:

图1.  二叉树的遍历运行结果截图

2.HUFFMAN编码步骤:
   1). 动态分配数组,存储哈夫曼编码;
   2).初始化Huffman树;
   3).输入权值;
   4).选择两个权最小的根节点;
   5).构造哈夫曼树;
   6).求出哈夫曼编码表;
其源程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 32767

typedef struct
{
char* HfmCode;/*动态分配数组,存储哈夫曼编码*/
char ch;
}code, *HuffmanCode;   
typedef struct
{
unsigned int weight ; /* 用来存放各个结点的权值*/
char ch;
unsigned int parent, LChild,RChild ; /*指向双亲、孩子结点的指针*/
}HTNode, * HuffmanTree; /*动态分配数组,存储哈夫曼树*/

void select(HuffmanTree *ht,int n, int *s1, int *s2)
{ /*ht,为树所在数组的头指针,n为允许查找的最大序号,s1,s2,返回最小的两个序号*/
int p1=MAX;
int p2=MAX; /*p1, p2用来记录最小的两个权, 要求p1<p2*/
int pn1=0;
int pn2=0, i; /*pn1, pn2 用来记录这两个权的序号*/
for(i=1;i<=n;i++)
if((*ht)[i].weight<p1 && (*ht)[i].parent==0)
{/*如果当前结点的权比p1小,那么p2的权赋为p1的权, p1的权为当前权,同时修改n1, pn2*/
/*查找的结点要求没有父结点*/
pn2=pn1;
p2=p1;
p1=(*ht)[i].weight;
pn1=i;
}
else
if((*ht)[i].weight<p2 &&(*ht)[i].parent==0)
{/*如果当前结点的权比p2小(由第一个if,当前权不比p1小),那么p2的权赋为当前权,同时修改pn2*/
p2=(*ht)[i].weight;
pn2=i;
}
*s1=pn1;
*s2=pn2; /*赋值返回*/
}
void CrtHuffmanTree(HuffmanTree *ht , int *w, char *st,int n)
{ /* w存放已知的n个权值,构造哈夫曼树ht */
/*由于是用数组的结构来存储树,故ht放的是数组的头指针*/
int m,i;
int s1,s2; /*在select函数中使用,用来存储最小权的结点的序号*/
m=2*n-1; /*可以计算H树的最大标号为2*n-1*/
/*因为最大标号为m,所以分配的空间为m+1*/
*ht=(HuffmanTree)malloc((m+1)*sizeof(HTNode)); /*0号单元未使用*/

for(i=1;i<=n;i++)
{/*1-n号放叶子结点,初始化*/
(*ht)[i].weight = w[i];
(*ht)[i].ch=st[i];
(*ht)[i].LChild = 0;
(*ht)[i].parent = 0;
(*ht)[i].RChild = 0;
}
for(i=n+1;i<=m;i++)
{
(*ht)[i].weight = 0;
(*ht)[i].LChild = 0;
(*ht)[i].parent = 0;
(*ht)[i].RChild = 0;
} /*非叶子结点初始化*/
for(i=n+1;i<=m;i++) /*创建非叶子结点,建哈夫曼树*/
{ /*在(*ht)[1]~(*ht)[i-1]的范围内选择两个parent为0且weight最小的结点,其序号分别赋值给s1、s2返回*/
select(ht,i-1,&s1,&s2);
(*ht)[s1].parent=i;
(*ht)[s2].parent=i; /*设置这两个最小的权的结点的父结点为i*/
(*ht)[i].LChild=s1;
(*ht)[i].RChild=s2; /*设置这个父结点的子结点*/
(*ht)[i].weight=(*ht)[s1].weight+(*ht)[s2].weight; /*设置这个父结点权*/
}
}/*哈夫曼树建立完毕*/
void outputHuffman(HuffmanTree HT, int m)
{ /*用的递归算法,从根结点开始遍历*/
if(m!=0)
{
printf("%d ", HT[m].weight);
outputHuffman(HT,HT[m].LChild);
outputHuffman(HT,HT[m].RChild);
}
}

void CrtHuffmanCode(HuffmanTree *ht, HuffmanCode *hc, int n)
/*从叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码*/
{
char *cd;
int i;
unsigned int c;
int start;
int p;
*hc=(HuffmanCode)malloc((n+1)*sizeof(code)); /*分配n个编码的头指针*/
cd=(char * )malloc(n * sizeof(char )); /*分配求当前编码的工作空间*/
cd[n-1]='\0'; /*从右向左逐位存放编码,首先存放编码结束符*/
for(i=1;i<=n;i++) /*求n个叶子结点对应的哈夫曼编码*/
{
(*hc)[i].ch=(*ht)[i].ch;
start=n-1; /*初始化编码起始指针*/
/*通过c是current的意思,表示向根结点靠拢的当前结点,序号从i开始
p是parent,始终指向c的父结点, p==0时c就是根结点*/
for(c=i,p=(*ht)[i].parent; p!=0; c=p,p=(*ht)[p].parent) /*从叶子到根结点求编码*/
if( (*ht)[p].LChild == c)
cd[--start]='0'; /*左分支标0*/
else
cd[--start]='1'; /*右分支标1*/
/*for循环结点条件为 *p=0表示c是根结点了,而每循环一次又由cd[--start]倒序记录了
每一步是父结点的左或右结点,也就是形成了H编码,并放在cd中*/
(*hc)[i].HfmCode=(char *)malloc((n-start)*sizeof(char)); /*为第i个编码分配空间*/
/*复制编码到对应的结点(当然是所有的需要编码的结点)*/
strcpy((*hc)[i].HfmCode,&cd[start]);
}
free(cd);
}

int findcode(HuffmanCode hc, char ch,int n)
{ /*可以理解hc为 字符<=>编码对照表*/
int i;
for(i=1;i<=n;i++)
if(hc[i].ch==ch)
return(i); /*如果一个字符在字符集中,那么返回这个字符的序号*/
return(0);
}
void main()
{
HuffmanTree HT;
HuffmanCode HC;
int *w; /*各字符的权重*/
char *st; /* 字符集,和st[i]和s[i]对应*/
int i,n; /*/ the number of elements;*/
int wei; /*/ the weight of a element;*/
int m;
printf("input the total number of the Huffman Tree:" );
scanf("%d",&n);
w=(int *)malloc((n+1)*sizeof(int));
st=(char *)malloc((n+1)*sizeof(int));
for(i=1;i<=n;i++) /*输入元素及其权重,存储在w,st中*/
{
printf("input the %d element and it's weight:",i);
fflush(stdin);
scanf("%c %d",&st[i],&wei);
w[i]=wei;
}
CrtHuffmanTree(&HT,w,st,n); /*构造H树*/
m = 2*n-1;
outputHuffman(HT,m); /*显示H树*/
printf("\n");
CrtHuffmanCode(&HT,&HC,n); /*根据H树,求每个字符的编码,放在HC中*/
for(i=1;i<=n;i++) /*输出编码*/
printf("%c编码为%s\n",HC[i].ch,HC[i].HfmCode);
}运行结果:
                    图2.   Huffman编码程序运行结果截图
三、实验结果分析和体会:
通过这次实验让我对所学内容有进一步理解,虽然程序写的有点吃力,但还是有一定收获。学习它颇有一定的难度,调试时出现了不少问题,但最终还是成功了,这次实验使我对学习数据结构有了更浓的兴趣,在以后的学习中我将更努力的学好每一章节,做好每个实验。
    做这次实验感觉比以前好多了,也许是有过一两次实验的锻炼,至少比以前顺畅多了。以前做一点就要翻书看看,不然就做不下去。这次看书的频率终于有所降低了。通过这次试验加深了我对所学基础知识的理解,树型结构是一类重要的非线性数据结构,对它的操作主要是进行一些遍历,先、中、后序的遍历我们都必须掌握。由于二叉树是一种非线性结构,每个结点都可能有两棵子树,因而需要寻找一种规律,以便使二叉树上的结点能排列在一个线性队列上,从而便于遍历。通过实验加深了自己对树的理解以及树的实现,以及各种遍历顺序。,二叉树是另一种树型结构的树,它的特点就是每个结点至多只有两棵子树(既二叉树不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒。遍历二叉树有可分为先序遍历二叉树,中序遍历二叉树和后序遍历二叉树。二叉树的遍历是以一定规则将二叉树的每个节点均被访问一次,而且仅被访问一次,这次实验最大的收获就是学会了对二叉树进行先、中、后序遍历。
    本次实验还使我进一步熟悉了链表的构造等相关操作。同样由于初次接触二叉树。虽然基本概念能够理解,但在实践过程中还是遇到了一些意想不到的困难。比如二叉树的初始化阶段,由于没有全面考虑到初始状态的特殊情况,导致非法储存单元被调用多次。很长一段时间都在解决或者说寻找这一严重的错误,这在以往的实验中从未遇到。我通过这次实践得到了很宝贵的经验和教训,相信对以后的实验有不小的帮助。
通过这次实验我在程序设计方面有了一定的提高!自己进一步熟悉了这个程序的建立过程,使自己又进一步的认识到C语言的重要性,在编程和调试的过程中,充分认识到自己C语言基础知识掌握的不扎实,在今后的学习过程中一定会在基础方面多努力。

你可能感兴趣的:(数据结构,编程,c,算法,D语言)