实验一:线性表
- 实验目的
(1)了解线性表的逻辑结构特性是数据元素之间存在着线性关系,在计算机中 表示这种关系有顺序存储结构和链式存储结构;
(2)掌握这两种存储结构的描述方法;
(3)掌握线性表的基本操作(查找、插入、删除) ;
(4)考虑时间和空间复杂度设计算法。 - 实验内容(实验题目与说明)
(1)创建一个顺序表,存放在数组A[N]中,元素的类型为整型,设计算法调整A,使其左边的所有元素小于0,右边的所有元素大于0(要求算法的时间复杂度和空 间复杂度均为 O(n)) 。
(2)建立一个循环单链表,其节点有prior,data 和 next三个域,其中data为数据域,存放元素的有效信息,next域为指针域,指向后继节点,prior 为指针域,它的值为NULL。编写一个算法将此表改为循环双链表。 - 算法设计(核心代码或全部代码)
void quickSwapList(int a[],int n)
{
int i = 0, j = n - 1 ; // n 为顺序表的长度,即数的个数
while( i < j ) {
while( a[i] < 0 && i < j )
i ++ ; // 找到左边大于等于 0 的数
while( a[j] >= 0 && i < j )
j -- ; // 找到右边小于 0 的数
if(i < j ) // 交换
{
int t = a[i] ;
a[i] = a[j] ;
a[j] = t ;
}
i++;
j--; } }
void singleCircleToDoubleCircleLinkList(struct Node *first) {
struct Node *p,*q; p=first; //工作指针 p 指向头结点
q=first->next; //工作指针 q 指向第 1 个结点
while(q!=first) {
q->prior=p; //置结点 q 的前驱为指针 p 指向的结点
p=p->next; //移动工作指针 p
q=q->next; //移动工作指针 q
}
q->prior=p; //置头结点的前驱为最后一个结点
}
-
运行与测试(测试数据和实验结果分析)
-
总结与心得
以上代码虽然书本上都有,但是经过我的学习,我觉得我都已经慢慢掌握了,可是涉及到双链表和循环链表的还不是很理解,后面的就更不用说了,还需要在下把力气了。
实验二:栈
-
实验目的
(1)理解栈的定义、特点及与线性表的异同;
(2)熟悉顺序栈的组织方法,栈满、栈空的判断条件及其描述;
(3)掌握栈的基本操作(进栈、退栈等) 。 -
实验内容(实验题目与说明)
(1)设计一个算法,将一般算术表达式转化为逆波兰表达式,并求逆波兰表达 式的值。
(2)设计两个栈 S1、 S2 都采用顺序栈方式, 并且共享一个存储区[0, MaxLen-1], 为了尽量利用空间,减少溢出的可能,可采用栈顶相向、迎面增长的存储方式,设计一个有关栈的入栈和出栈算法。 -
算法设计(核心代码或全部代码)
/*计算后缀表达式的值*/
float ComputeExpress(char a[])
{
OpStack S; /*定义一个操作数栈*/
int i=0,value;
float x1,x2;
float result;
S.top=-1; /*初始化栈*/
while(a[i]!='\0') /*依次扫描后缀表达式中的每个字符*/
{
if(a[i]!=' '&&a[i]>='0'&&a[i]<='9') /*如果当前字符是数字字符*/
{
value=0;
while(a[i]!=' ') /*如果不是空格,说明数字字符是两位数以上的数字字符*/
{
value=10*value+a[i]-'0';
i++;
}
S.top++;
S.data[S.top]=value; /*处理之后将数字进栈*/
}
else /*如果当前字符是运算符*/
{
switch(a[i]) /*将栈中的数字出栈两次,然后用当前的运算符进行运算,再将结果入栈*/
{
case '+':
x1=S.data[S.top];
S.top--;
x2=S.data[S.top];
S.top--;
result=x1+x2;
S.top++;
S.data[S.top]=result;
break;
case '-':
x1=S.data[S.top];
S.top--;
x2=S.data[S.top];
S.top--;
result=x2-x1;
S.top++;
S.data[S.top]=result;
break;
case '*':
x1=S.data[S.top];
S.top--;
x2=S.data[S.top];
S.top--;
result=x1*x2;
S.top++;
S.data[S.top]=result;
break;
case '/':
x1=S.data[S.top];
S.top--;
x2=S.data[S.top];
S.top--;
result=x2/x1;
S.top++;
S.data[S.top]=result;
break;
}
i++;
}
}
if(!S.top!=-1) /*如果栈不空,将结果出栈,并返回*/
{
result=S.data[S.top];
S.top--;
if(S.top==-1)
return result;
else
{
printf("表达式错误");
exit(-1);
}
}
}
int PushStack(SequenStack *S,DataType e,int flag)
/*将元素e入共享栈。进栈成功返回1,否则返回0*/
{
if(S->top[0]==S->top[1]+1) /*如果共享栈已满*/
return 0; /*返回0,进栈失败*/
switch(flag)
{
case 1: /*当flag为1,表示将元素进左端的栈*/
S->stack[S->top[0]]=e; /*元素进栈*/
S->top[0]++; /*修改栈顶指针*/
break;
case 2: /*当flag为2,表示将元素要进右端的栈*/
S->stack[S->top[1]]=e; /*元素进栈*/
S->top[1]--; /*修改栈顶指针*/
break;
default:
return 0;
}
return 1; /*返回1,进栈成功*/
}
int PopStack(SequenStack *S,DataType *e,int flag)
{
switch(flag) /*在出栈操作之前,判断哪个栈要进行出栈操作*/
{
case 1: /*为1,表示左端的栈需要出栈操作*/
if(S->top[0]==0) /*左端的栈为空*/
return 0; /*返回0,出栈操作失败*/
S->top[0]--; /*修改栈顶指针,元素出栈操作*/
*e=S->stack[S->top[0]]; /*将出栈的元素赋给e*/
break;
case 2: /*为2,表示右端的栈需要出栈操作*/
if(S->top[1]==MaxLen-1) /*右端的栈为空*/
return 0; /*返回0,出栈操作失败*/
S->top[1]++; /*修改栈顶指针,元素出栈操作*/
*e=S->stack[S->top[1]]; /*将出栈的元素赋给e*/
break;
default:
return 0;
}
return 1; /*返回1,出栈操作成功*/
}
-
运行与测试(测试数据和实验结果分析)
-
总结与心得
这是数据结构的第二个实验,主要涉及到了栈的构造构造及应用,注意一下代码中注释的地方,这边的*S.top++ = e;等于 *S.top = e;S.top++;这是Push的关键。同时注意一下malloc和realloc的用法。
实验三:队列
-
实验目的
(1) 理解队列的定义、特点及与线性表的异同;
(2) 熟悉队列的组织方法,队列满、队列空的判断条件及其描述;
(3) 掌握队列的基本操作(入队、出队等) 。 -
实验内容(实验题目与说明)
(1)假设以数组 sequ[MaxSize]存放环形队列的元素, 同时 Rear 和 Len 分别指示 环形队列中队尾元素的位置和内含元素的个数。设计相应的入队和出队算法。
(2)某汽车轮渡口,过江渡船每次能载 10 辆车过江。过江车辆分别为客车类和 货车类,上船有如下规定:同类车先到先上船,客车先于货车上渡船,且每上 4 辆客 车,才允许上一辆货车;若等待客车不足 4 辆则以货车代替;若无货车等待则允许客 车都上船。设计一个算法模拟渡口管理。 -
算法设计(核心代码或全部代码)
//入队
void entryQueue(int x) {
if(Len==MaxSize)//队满 {
printf("队列已满,不能入队\n");
return; }
Rear=(Rear+1)%MaxSize;//移动队尾指针
sequ[Rear]=x;//入队
Len++;//长度加 1
return; }
//出队,返回出队元素
int exitQueue(void) {
int x,First;//队头元素的下标
if(Len==0)//队空 {
printf("队列已空,不能出队\n");
return -1; }
First=((Rear+MaxSize)-Len+1)%MaxSize;//计算队头元素的下标
x=sequ[First];//获得队头元素
Len--;//长度减 1
return x;//返回队头元素 }
-
运行与测试(测试数据和实验结果分析)
-
总结与心得
通过这一次的实验,我不仅实现了栈和队列的算法,还解决了上次作业遗留下来的问题。通过百度,我知道了“ ? : ”操作符是需要返回值的,所以问号后面的和冒号后面的都是需要一个表达式,而不是一个完整的语句。那么 top==NULL?return 1:return 0; 语句就应该改为 return (top == -1)?1:0; 语句,程序才得以运行成功。
实验四:二叉树
-
实验目的
(1)掌握二叉树的结构特性和二叉链表存储结构;
(2)理解二叉树、完全二叉树、满二叉树的概念和存储特点;
(3)掌握二叉树遍历的递归和非递归方法。 -
实验内容(实验题目与说明)
(1)假设二叉树采用链接存储方式存储,分别编写一个二叉树先序遍历的递归算法和非递归算法。
(2)一棵完全二叉树以顺序方式存储,设计一个递归算法,对该完全二叉树进行中序遍历。 -
算法设计(核心代码或全部代码)
struct biTreeNode *creatBiTree(struct biTreeNode *T) {
char ch;
static int i=0;
ch=node[i]; //数组 node 存放了要创建的二叉树的扩展先序遍历序列
i++;
if(ch=='#') {
T=NULL; //#代表空子树;
}
else {
T=(struct biTreeNode*)malloc(sizeof(struct biTreeNode));
if(!T)
exit(0);
T->data = ch; //数据域为 ch
T->leftChild=creatBiTree(T->leftChild); //递归建立左子树
T->rightChild=creatBiTree(T->rightChild); //递归建立右子树 }
return T; }
void seqPreOrder(int i) {
if(i==0) //递归调用的结束条件
return;
else {
printf("%2c",node[i]);//输出根结点
if(2*i<=node[0])
seqPreOrder(2*i);//先序遍历 i 的左子树
else
seqPreOrder(0);
if(2*i+1<=node[0])
seqPreOrder(2*i+1);//先序遍历 i 的右子树
else
seqPreOrder(0); } }
- 运行与测试(测试数据和实验结果分析)
- 总结与心得
通过本次作业,加深了对递归的理解,掌握了递归的用法。
对于实验四:判断两棵二叉树是否相等。当判断到字符不同时,由于是递归函数,不能只用简单的return 0;退出函数,因为这样只会退到上一层函数。解决方法:定义一个全局变量flag,初始值为1,当判断到字符不同时令flag=0;在判断函数Equal2中加上判断:if(flag==0) return 0;由于flag是全局变量,每一层Equal2函数中的flag都会变为0,所以可以完全退出递归。
实验五:哈夫曼树
-
实验目的
(1)理解哈夫曼树的概念、结构特性和哈夫曼编码原理;
(2)掌握构造哈夫曼树的基本方法;
(3)掌握运用哈夫曼树进行哈夫曼编码的方法; -
实验内容(实验题目与说明)
根据哈夫曼(Huffman)编码的原理,编写一个程序,在用户输入节点权重的基础 上建立它的哈夫曼编码。 -
算法设计(核心代码或全部代码)
void huffmanTreeCode(int n) {
char hc[MaxSize];
int hcLen;
int i,j,k,parent,p;
for(i=0;i<n;i++) {
hcLen=0;
parent=huffmanTree[i].parent;//待编码字符的双亲结点下标
p=i;
while(parent!=-1)//未到达根结点
{
if(huffmanTree[parent].lChild==p)//是左孩子
hc[hcLen]='0',hcLen++;
else if(huffmanTree[parent].rChild==p)//是右孩子
hc[hcLen]='1',hcLen++;
p=parent;
parent=huffmanTree[parent].parent;//继续向根结点查找
}
for(j=0,k=hcLen-1;j<hcLen;j++,k--)//将编码写入相应字符数组
huffmanTree[i].hCode[j]=hc[k];
huffmanTree[i].hCode[j]='\0';//加上字符串结束符
} return; }
-
运行与测试(测试数据和实验结果分析)
-
总结与心得
哈弗曼编码是一种常用的编码形式。甚至是WinRAR、ZIP、7z等压缩软件虽然不是是用哈弗曼编码进行压缩解压,毕竟纯哈弗曼编码的压缩率还是不足,但是至少它们多多少少都有点哈弗曼编码的思想。所以这是一种很不错的算法。
实验八:排序
-
实验目的
(1)理解各类内部排序的基本思想;
(2)掌握各类内部排序的基本方法;
(3)了解各类内部排序的优缺点。 -
实验内容(实验题目与说明)
(1)编写一个双向冒泡排序算法,即在排序过程中以交替的正、反两个方向进 行遍历。若第一趟把关键字最大的记录放到最末尾,则第二趟把关键字最小的记录放 到最前端,如此反复进行之。
(2)设计一个进行堆排序的算法。 -
算法设计(核心代码或全部代码)
void doubleBubble(int r[],int n) {
int flag=1;
int i=0,j;
int temp;
while (flag==1) {
flag=0;
for(j=n-i-1;j>=i+1;j--)//反向遍历找最小值
if(r[j]<r[j-1]) {
flag=1; //能交换说明没有排好序,要继续
temp=r[j];
r[j]=r[j-1];
r[j-1]=temp; }
for(j=i+1;j<n-1;j++)//正向遍历找最大值
if(r[j]>r[j+1]) {
flag=1; //能交换说明没有排好序,要继续
temp=r[j];
r[j]=r[j+1];
r[j+1]=temp; }
i++; } return; }
void sift(int r[],int k,int m) {
int i,j;
i=k;//i 指向被筛选结点
j=2*i;// j 指向结点 i 的左孩子
while(j<=m) {
if(j<m && r[j]<r[j+1])//比较 i 的左右孩子, j 指向较大者
j++; if(r[i]>r[j])//根结点已经大于左右孩子中的较大者
break;
else {
r[0]=r[i];//交换根结点与结点 j
r[i]=r[j];
r[j]=r[0];
i=j;//被筛选结点位于原来结点的 j 的位置
j=2*i; }
} return; }
-
运行与测试(测试数据和实验结果分析)
-
总结与心得
- 在堆排序中,我们在做的是:
- 先将序列进行m/2次调整为大顶堆(m为结点个数,自底第一个非叶子结点开始向上);
- 再将头结点与尾结点作交换,然后当前序列的结点个数减1,再作一次从的调整(即将当前的头结点尽量的往下沉,直到它的孩子结点的值都比它小);
*重复操作2,直到当前的元素个数为1即可结束!