实验一:顺序表$(2021.10.08)$
一、实验目的
熟悉线性表顺序存储结构的类型说明及基本操作的实现
二、实验内容
利用顺序表的基本操作实现两个有序表合并(教材算法2.2)
补充顺序表中下列基本操作的定义:
CLearList
,ListEmpty
,LocateElem
,ListDelete
记录实验过程并总结实验经验,完成实验报告。
三、实验步骤
新建一个文件夹,并在其中新建一个扩展名为
.cpp
的文件;将实验材料(附件)中的
c1.h
和sqlist.cpp
文件复制到同一个文件夹中。其中,
c1.h
包含程序中常用的头文件及宏定义,以后每次都要记得将该文件包括到你的源程序中。
sqlist.cpp
包含线性表顺序存储结构的定义和部分基本操作的实现。
- 在你的
cpp
源文件(主文件)中,输入以下三个命令(语句)
typedef int ElemType
#include "c1.h"
#include "sqlist.cpp"
- 在
cpp
主文件中写实现两有序表合并的函数MergeList
请注意:
写完一个函数后,应立即进行编译。
编译通过后,再开始编写其它函数。
- 在
main
函数中实现两线性表La
,Lb
的初始化输入,然后调用MergeList
实现两有序表合并,并将结果输出。
// 实验.cpp(程序名)
typedef int ElemType;// 要加分号
# include "c1.h"
# include "sqlist.cpp"
void MergeList_Sq(SqList la,SqList lb,SqList &lc){
// 已知顺序线性表La和Lb的元素按值非递减排序
// 归并La和Lb得到新的肾虚线性表Lc,Lc的元素也按值非递减排列
InitList(lc);
int i,j,k;
i=j=1;
k=0;
int la_len,lb_len;
la_len = ListLength(la);
lb_len = ListLength(lb);
ElemType ai,bj;
while((i<=la_len)&&(j<=lb_len)){// la与lb均非空
GetElem(la,i,ai);
GetElem(lb,j,bj);
if(ai<=bj){
ListInsert(lc,++k,ai);
++i;
}else{
ListInsert(lc,++k,bj);
++j;
}
}
while(i<=la_len){
GetElem(la,i++,ai);
ListInsert(lc,++k,ai);
}
while(j<=lb_len){
GetElem(lb,j++,bj);
ListInsert(lc,++k,bj);
}
}// MergeList_Sq
void print(ElemType c){
printf("%d ",c);
}
int main(){
SqList la,lb,lc;
int j,a[4]={1,3,5,7},b[5]={2,4,6,8,10};
InitList(la);
for(j=1;j<=4;j++){
ListInsert(la,j,a[j-1]);
}
printf("la=");
ListTraverse(la,print);
InitList(lb);
for(j=1;j<=5;j++){
ListInsert(lb,j,b[j-1]);
}
printf("lb=");
ListTraverse(lb,print);
MergeList_Sq(la,lb,lc);
printf("lc=");
ListTraverse(lc,print);
DestroyList(la);
DestroyList(lb);
DestroyList(lc);
}
实验结束后,请将源程序(.h
或 .cpp
)和实验报告打包后上传至课程中心的网站。
四、问题
如果线性表的元素类型为字符型,应如何修改,请实验之。
// 问题一.cpp typedef char ElemType;// 要加分号 # include "c1.h" # include "sqlist.cpp" void MergeList_Sq(SqList la,SqList lb,SqList &lc){ // 已知顺序线性表La和Lb的元素按值非递减排序 // 归并La和Lb得到新的肾虚线性表Lc,Lc的元素也按值非递减排列 InitList(lc); int i,j,k; i=j=1; k=0; int la_len,lb_len; la_len = ListLength(la); lb_len = ListLength(lb); ElemType ai,bj; while((i<=la_len)&&(j<=lb_len)){// la与lb均非空 GetElem(la,i,ai); GetElem(lb,j,bj); if(ai<=bj){ ListInsert(lc,++k,ai); ++i; }else{ ListInsert(lc,++k,bj); ++j; } } while(i<=la_len){ GetElem(la,i++,ai); ListInsert(lc,++k,ai); } while(j<=lb_len){ GetElem(lb,j++,bj); ListInsert(lc,++k,bj); } }// MergeList_Sq void print(ElemType c){ printf("%d ",c); } int main(){ SqList la,lb,lc; int j,a[4]={'1','3','5','7'},b[5]={'2','4','6','8','10'}; InitList(la); for(j=1;j<=4;j++){ ListInsert(la,j,a[j-1]); } printf("la="); ListTraverse(la,print); InitList(lb); for(j=1;j<=5;j++){ ListInsert(lb,j,b[j-1]); } printf("lb="); ListTraverse(lb,print); MergeList_Sq(la,lb,lc); printf("lc="); ListTraverse(lc,print); DestroyList(la); DestroyList(lb); DestroyList(lc); }
ListTraverse
可否做修改线性表各元素的操作?
例如:在上一题的基础上,将每个元素转换为大写字母,或者将每个元素转换为小写字母等。
应作何修改?请实验之。// 问题二.cpp typedef char ElemType;// 要加分号 # include "c1.h" # include "sqlist.cpp" void print(ElemType c){ printf("%c ",c); } void up(SqList &L){ int m; ElemType am; for(m=1;m<=L.length;m++){ GetElem(L,m,am); if(am>='a'&&am<='z'){ *(L.elem+m-1)=*(L.elem+m-1)+'A'-'a'; } } } void down(SqList &L){ int m; ElemType am; for(m=1;m<=L.length;m++){ GetElem(L,m,am); if(am>='A'&&am<='Z'){ *(L.elem+m-1)=*(L.elem+m-1)-('A'-'a'); } } } void MergeList_Sq(SqList la,SqList lb,SqList &lc){ // 已知顺序线性表La和Lb的元素按值非递减排序 // 归并La和Lb得到新的肾虚线性表Lc,Lc的元素也按值非递减排列 InitList(lc); int i,j,k; i=j=1; k=0; int la_len,lb_len; la_len = ListLength(la); lb_len = ListLength(lb); ElemType ai,bj; while((i<=la_len)&&(j<=lb_len)){// la与lb均非空 GetElem(la,i,ai); GetElem(lb,j,bj); if(ai<=bj){ ListInsert(lc,++k,ai); ++i; }else{ ListInsert(lc,++k,bj); ++j; } } while(i<=la_len){ GetElem(la,i++,ai); ListInsert(lc,++k,ai); } while(j<=lb_len){ GetElem(lb,j++,bj); ListInsert(lc,++k,bj); } }// MergeList_Sq int main(){ SqList la,lb,lc; int j,a[4]={'a','c','e','g'},b[7]={'b','d','f','h','j'}; InitList(la); for(j=1;j<=4;j++){ ListInsert(la,j,a[j-1]); } printf("la="); up(la); ListTraverse(la,print); InitList(lb); for(j=1;j<=5;j++){ ListInsert(lb,j,b[j-1]); } printf("lb="); up(lb); ListTraverse(lb,print); MergeList_Sq(la,lb,lc); printf("lc="); ListTraverse(lc,print); down(lc); printf("lc_down="); ListTraverse(lc,print); DestroyList(la); DestroyList(lb); DestroyList(lc); }
在
sqlist.cpp
中CLearList
,ListEmpty
,LocateElem
,ListDelete
操作还未完成,请你把它们补充完整。本人作业:
// SqList.cpp 顺序表示的线性表 #define LIST_INIT_SIZE 10 // 线性表存储空间的初始分配量 #define LISTINCREMENT 2 // 线性表存储空间的分配增量 struct SqList { ElemType *elem; // 存储空间基址 int length; // 当前长度 int listsize; // 当前分配的存储容量(以sizeof(ElemType)为单位) }; Status InitList(SqList &L) // 算法2.3 { // 操作结果:构造一个空的顺序线性表 L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType)); if(!L.elem) exit(OVERFLOW); // 存储分配失败 L.length=0; // 空表长度为0 L.listsize=LIST_INIT_SIZE; // 初始存储容量 return OK; } Status DestroyList(SqList &L) { // 初始条件:顺序线性表L已存在。操作结果:销毁顺序线性表L free(L.elem); L.elem=NULL; L.length=0; L.listsize=0; return OK; } Status ClearList(SqList &L)// 存疑 { // 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 free(L.elem); return OK; } Status ListEmpty(SqList L)// 存疑 { // 初始条件:顺序线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE if(GetElem(L,0)==NULL){ return TRUE; }else{ return FALSE; } } int ListLength(SqList L) { // 初始条件:顺序线性表L已存在。操作结果:返回L中数据元素个数 return L.length; } Status GetElem(SqList L,int i,ElemType &e) { // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) // 操作结果:用e返回L中第i个数据元素的值 if(i<1||i>L.length) exit(ERROR); e=*(L.elem+i-1); return OK; } int LocateElem(SqList L,ElemType e,Status(*compare)(ElemType,ElemType))// 存疑 { // 初始条件:顺序线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0) // 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。 // 若这样的数据元素不存在,则返回值为0。算法2.6 int i=0; if(!(GetElem(L,i)==(*compare)(e) || GetElem(L,i)==NULL)){ i++; }else{ return i; } if(GetElem(L,i)==NULL){ return 0; } } Status PriorElem(SqList L,ElemType cur_e,ElemType &pre_e) { // 初始条件:顺序线性表L已存在 // 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱, // 否则操作失败,pre_e无定义 int i=2; ElemType *p=L.elem+1; while(i<=L.length&&*p!=cur_e) { p++; i++; } if(i>L.length) return INFEASIBLE; else { pre_e=*--p; return OK; } } Status NextElem(SqList L,ElemType cur_e,ElemType &next_e) { // 初始条件:顺序线性表L已存在 // 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继, // 否则操作失败,next_e无定义 int i=1; ElemType *p=L.elem; while(i<L.length&&*p!=cur_e) { i++; p++; } if(i==L.length) return INFEASIBLE; else { next_e=*++p; return OK; } } Status ListInsert(SqList &L,int i,ElemType e) // 算法2.4 { // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1 // 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 ElemType *newbase,*q,*p; if(i<1||i>L.length+1) // i值不合法 return ERROR; if(L.length>=L.listsize) // 当前存储空间已满,增加分配 { if(!(newbase=(ElemType *)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType)))) exit(OVERFLOW); // 存储分配失败 L.elem=newbase; // 新基址 L.listsize+=LISTINCREMENT; // 增加存储容量 } q=L.elem+i-1; // q为插入位置 for(p=L.elem+L.length-1;p>=q;--p) // 插入位置及之后的元素右移 *(p+1)=*p; *q=e; // 插入e ++L.length; // 表长增1 return OK; } Status ListDelete(SqList &L,int i,ElemType &e) // 算法2.5 // 自己按书上写,不存疑 { // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) // 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 p = &(L.elem[i-1]); e = *p; q = L.elem + L.length - 1; for(++p;p<=q;p++){ *(p-1) = *p;// 被删除元素之后的元素左移一位 } L.length--; return OK; } Status ListTraverse(SqList L,void(*vi)(ElemType )) { // 初始条件:顺序线性表L已存在 // 操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败 // vi()的形参加'&',表明可通过调用vi()改变元素的值 ElemType *p; int i; p=L.elem; for(i=1;i<=L.length;i++) vi(*p++); std::cout<<endl;// endl就相当于输出的时候回车 /*结合 #include<iostream> // cout,cin using namespace std; 使用 */ return OK; }
与同学作业比对后修改的个人作业:
// SqList.cpp 顺序表示的线性表
#define LIST_INIT_SIZE 10 // 线性表存储空间的初始分配量
#define LISTINCREMENT 2 // 线性表存储空间的分配增量
struct SqList
{
ElemType *elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量(以sizeof(ElemType)为单位)
};
Status InitList(SqList &L) // 算法2.3
{ // 操作结果:构造一个空的顺序线性表
L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if(!L.elem)
exit(OVERFLOW); // 存储分配失败
L.length=0; // 空表长度为0
L.listsize=LIST_INIT_SIZE; // 初始存储容量
return OK;
}
Status DestroyList(SqList &L)
{ // 初始条件:顺序线性表L已存在。操作结果:销毁顺序线性表L
free(L.elem);
L.elem=NULL;
L.length=0;
L.listsize=0;
return OK;
}
Status ClearList(SqList &L)// 修改版
{ // 初始条件:顺序线性表L已存在。操作结果:将L重置为空表
if(!L.elem){// 内存分配失败
return ERROR;
}
L.length = 0;
return OK;
}
Status ListEmpty(SqList L)// 修改版
{ // 初始条件:顺序线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE
if(!L.elem){
return ERROR;
}
if(L.length == 0){
return TRUE;
}
else return FALSE;
}
int ListLength(SqList L)
{ // 初始条件:顺序线性表L已存在。操作结果:返回L中数据元素个数
return L.length;
}
Status GetElem(SqList L,int i,ElemType &e)
{ // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
// 操作结果:用e返回L中第i个数据元素的值
if(i<1||i>L.length)
exit(ERROR);
e=*(L.elem+i-1);
return OK;
}
int LocateElem(SqList L,ElemType e,Status(*compare)(ElemType,ElemType))// 修改版
{ // 初始条件:顺序线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
// 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0。算法2.6
ElemType *p;// p为指针
int i = 1;// i为计数器
p = L.elem;
while(i<=L.length && !(*compare)(*p++,e)){
i++;
}
if(i<=L.length){
return i;
}
else return 0;
}
Status PriorElem(SqList L,ElemType cur_e,ElemType &pre_e)
{ // 初始条件:顺序线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 否则操作失败,pre_e无定义
int i=2;
ElemType *p=L.elem+1;
while(i<=L.length&&*p!=cur_e)
{
p++;
i++;
}
if(i>L.length)
return INFEASIBLE;
else
{
pre_e=*--p;
return OK;
}
}
Status NextElem(SqList L,ElemType cur_e,ElemType &next_e)
{ // 初始条件:顺序线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 否则操作失败,next_e无定义
int i=1;
ElemType *p=L.elem;
while(i<L.length&&*p!=cur_e)
{
i++;
p++;
}
if(i==L.length)
return INFEASIBLE;
else
{
next_e=*++p;
return OK;
}
}
Status ListInsert(SqList &L,int i,ElemType e) // 算法2.4
{ // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1
// 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
ElemType *newbase,*q,*p;
if(i<1||i>L.length+1) // i值不合法
return ERROR;
if(L.length>=L.listsize) // 当前存储空间已满,增加分配
{
if(!(newbase=(ElemType *)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType))))
exit(OVERFLOW); // 存储分配失败
L.elem=newbase; // 新基址
L.listsize+=LISTINCREMENT; // 增加存储容量
}
q=L.elem+i-1; // q为插入位置
for(p=L.elem+L.length-1;p>=q;--p) // 插入位置及之后的元素右移
*(p+1)=*p;
*q=e; // 插入e
++L.length; // 表长增1
return OK;
}
Status ListDelete(SqList &L,int i,ElemType &e) // 算法2.5 // 修改版
{ // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
// 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
ElemType *p,*q;
if((1>i)||(i>L.length)){
return ERROR;
}
p = &(L.elem[i-1]);
e = *p;
q = L.elem + L.length - 1;
for(++p;p<=q;p++){
*(p-1) = *p;// 被删除元素之后的元素左移一位
}
L.length--;
return OK;
}
Status ListTraverse(SqList L,void(*vi)(ElemType ))
{ // 初始条件:顺序线性表L已存在
// 操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
// vi()的形参加'&',表明可通过调用vi()改变元素的值
ElemType *p;
int i;
p=L.elem;
for(i=1;i<=L.length;i++)
vi(*p++);
std::cout<<endl;// endl就相当于输出的时候回车
/*结合
#include<iostream> // cout,cin
using namespace std;
使用
*/
return OK;
}
另一位同学的作业:
// SqList.cpp 顺序表示的线性表
#define LIST_INIT_SIZE 10 // 线性表存储空间的初始分配量
#define LISTINCREMENT 2 // 线性表存储空间的分配增量
struct SqList
{
ElemType *elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量(以sizeof(ElemType)为单位)
};
Status InitList(SqList &L) // 算法2.3
{ // 操作结果:构造一个空的顺序线性表
L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if(!L.elem)
exit(OVERFLOW); // 存储分配失败
L.length=0; // 空表长度为0
L.listsize=LIST_INIT_SIZE; // 初始存储容量
return OK;
}
Status DestroyList(SqList &L)
{ // 初始条件:顺序线性表L已存在。操作结果:销毁顺序线性表L
free(L.elem);
L.elem=NULL;
L.length=0;
L.listsize=0;
return OK;
}
Status ClearList(SqList &L)
{ // 初始条件:顺序线性表L已存在。操作结果:将L重置为空表
L.length=0;//若已存在置空
return OK;
}
Status ListEmpty(SqList L)
{ // 初始条件:顺序线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE
if (L.length==0)//判断是否为空
{
return TRUE;
}
else
{
return FALSE;
}
}
int ListLength(SqList L)
{ // 初始条件:顺序线性表L已存在。操作结果:返回L中数据元素个数
return L.length;
}
Status GetElem(SqList L,int i,ElemType &e)
{ // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
// 操作结果:用e返回L中第i个数据元素的值
if(i<1||i>L.length)
exit(ERROR);
e=*(L.elem+i-1);
return OK;
}
int compare(ElemType a,ElemType b)//定义了一个比较函数
{
if (a==b) return 1;
return 0;
}
int LocateElem(SqList L,ElemType e,Status(*compare)(ElemType,ElemType))
{ // 初始条件:顺序线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
// 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0。算法2.6
int i=1;
for (i;i<=L.length;i++)
{
if (compare(L.elem[i-1],e)) return i;
}
return 0;
}
Status PriorElem(SqList L,ElemType cur_e,ElemType &pre_e)
{ // 初始条件:顺序线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 否则操作失败,pre_e无定义
int i=2;
ElemType *p=L.elem+1;
while(i<=L.length&&*p!=cur_e)
{
p++;
i++;
}
if(i>L.length)
return INFEASIBLE;
else
{
pre_e=*--p;
return OK;
}
}
Status NextElem(SqList L,ElemType cur_e,ElemType &next_e)
{ // 初始条件:顺序线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 否则操作失败,next_e无定义
int i=1;
ElemType *p=L.elem;
while(i<L.length&&*p!=cur_e)
{
i++;
p++;
}
if(i==L.length)
return INFEASIBLE;
else
{
next_e=*++p;
return OK;
}
}
Status ListInsert(SqList &L,int i,ElemType e) // 算法2.4
{ // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1
// 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
ElemType *newbase,*q,*p;
if(i<1||i>L.length+1) // i值不合法
return ERROR;
if(L.length>=L.listsize) // 当前存储空间已满,增加分配
{
if(!(newbase=(ElemType *)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType))))
exit(OVERFLOW); // 存储分配失败
L.elem=newbase; // 新基址
L.listsize+=LISTINCREMENT; // 增加存储容量
}
q=L.elem+i-1; // q为插入位置
for(p=L.elem+L.length-1;p>=q;--p) // 插入位置及之后的元素右移
*(p+1)=*p;
*q=e; // 插入e
++L.length; // 表长增1
return OK;
}
Status ListDelete(SqList &L,int i,ElemType &e) // 算法2.5
{ // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
// 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
if (i<1||i>L.length) return ERROR;//i不合法或表空
e=L.elem[i-1];//被删除元素的值赋给e
int j;
for (j=i;j<=L.length;j++)
{
L.elem[j-1]=L.elem[j];//被删除元素之后的元素前移
}
L.length--;//表长减一
return OK;
}
Status ListTraverse(SqList &L,void(*vi)(ElemType ))// 由于在程序中定义了不同的vi具有不同的含义,所以未在这里给出具体vi函数,而在具体的操作文件中给出了注释
{ // 初始条件:顺序线性表L已存在
// 操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
// vi()的形参加'&',表明可通过调用vi()改变元素的值
ElemType *p;
int i;
p=L.elem;
for(i=1;i<=L.length;i++)
vi(*p++);
//cout<<endl;
return OK;
}
void amin()
{
}
五、附件
// c1.h
#include<string.h>
#include<ctype.h>
#include<malloc.h> // malloc()等
#include<limits.h> // INT_MAX等
#include<stdio.h> // EOF(=^Z或F6),NULL
#include<stdlib.h> // atoi()
#include<io.h> // eof()
#include<math.h> // floor(),ceil(),abs()
#include<process.h> // exit()
#include<iostream> // cout,cin
using namespace std;
// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
// #define OVERFLOW -2 因为在math.h中已定义OVERFLOW的值为3,故去掉此行
typedef int Status; // Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int Boolean; // Boolean是布尔类型,其值是TRUE或FALSE
// sqlist.cpp
// 见实验三
六、发现
调用 iostream.h
头文件方式
在 $DEV \ C$++ 中调用头文件 iostream.h
需使用如下语法(因为 $C$ ++ 文件没有 $.h$ 后缀)
// .h文件中
#include<iostream> // cout,cin
using namespace std;
// main函数文件中
std::cout<<endl;// endl就相当于输出的时候回车
%x 意义
%c,%y这些代表你要输出的数据的数据类型;
%d 表示输出十进制有符号的整数;
%u 十进制无符号整数;
%f 表示输出浮点数;
%s表示输出 字符串;
%c表示输出单个字符;
%p表示输出指针的值;
%e表示输出指数形式的浮点数;
%x, %X 表示输出无符号以十六进制表示的整数;
%0 表示输出无符号以八进制表示的整数;
%g表示输出自动选择合适的表示法。
实验二:链式表$(2021.10.22)$
一、实验目的
熟悉线性表的链式存储结构的类型说明和基本操作定义
二、实验内容
利用基本操作实现两个有序表合并(教材算法2.2)
补充未完成的基本操作:
InitList
,DestroyList
,ListLength
,ListTraverse
,GetElem
,ListInsert
记录实验过程并总结实验经验,完成实验报告
三、实验步骤
新建一个文件夹,并在其中新建一个扩展名为
.cpp
的文件。将实验材料(附件)中的
c1.h
和linklist.cpp
文件复制到同一个文件夹中。
其中,c1.h
中包含程序中常用的头文件及宏定义,以后每次都要记得将该文件包括到你的源程序中。
linklist.cpp
中包含线性表顺序存储结构的定义和部分基本操作的实现。
- 新建一个
MergeLink.cpp
源文件(主文件)中,在其中输入以下三个命令(语句)及一个空的main
函数
typedef int ElemType;
#include "c1.h"
#include "linklist.cpp"
main()
{
}
- 在
linklist.cpp
实现两有序表合并所需要的基本操作:InitList
,DestroyList
,ListTraverse
,GetElem
,ListInsert
请注意:写完一个函数后,应回到 MergeLink.cpp
进行编译。测试正确后,再开始写其它函数
// linklist.cpp
// 线性表的单链表存储结构
typedef struct LNode
{
ElemType data;
LNode *next;
}*LinkList;
// 单链表线性表的基本操作(12个)
Status InitList(LinkList &L) //*************
{ // 操作结果:构造一个空的线性表L
L = (LinkList)malloc(sizeof(LNode));//分配空间
if(!L){
exit(OVERFLOW);
}
L -> next = NULL;
return OK;
}
Status DestroyList(LinkList &L) //*************
{ // 初始条件:线性表L已存在。操作结果:销毁线性表L
LinkList p,q;
p = L -> next;
while(p){
q = p -> next; //用q保存p->next的值,方便free()操作
free(p); //同时删除p的data域与next域
p = q;
}
L = NULL; //置空L指针
return OK;
}
Status ClearList(LinkList L) // 不改变L
{ // 初始条件:线性表L已存在。操作结果:将L重置为空表
LinkList p,q;
p=L->next; // p指向第一个结点
while(p) // 没到表尾
{
q=p->next;
free(p);
p=q;
}
L->next=NULL; // 头结点指针域为空
return OK;
}
Status ListEmpty(LinkList L)
{ // 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE
if(L->next) // 非空
return FALSE;
else
return TRUE;
}
int ListLength(LinkList L) //*************
{ // 初始条件:线性表L已存在。操作结果:返回L中数据元素个数
LinkList p;
int i = 0;
p = L -> next;//p指向首元结点
while(p != NULL){ //p非空
p = p -> next;
i++;
}
return i;
}
Status GetElem(LinkList L,int i,ElemType &e) // 算法2.8
{ // L为带头结点的单链表的头指针。当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
LinkList p;
int j = 1; //初始化
p = L -> next; //p指向首元结点
while(p && j<1){ //顺指针向后查找,直到 p 指向第 i 个元素或 p 为空
p = p -> next;
++j;
}
if(!p || j>i){ //i>l.len或i<1情况
return ERROR;
}
e = p -> data; //将p结点中的data域值赋给e
return OK;
}
int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{ // 初始条件: 线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
// 操作结果: 返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0
int i=0;
LinkList p=L->next;
while(p)
{
i++;
if(compare(p->data,e)) // 找到这样的数据元素
return i;
p=p->next;
}
return 0;
}
Status PriorElem(LinkList L,ElemType cur_e,ElemType &pre_e)
{ // 初始条件: 线性表L已存在
// 操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 返回OK;否则操作失败,pre_e无定义,返回INFEASIBLE
LinkList q,p=L->next; // p指向第一个结点
while(p->next) // p所指结点有后继
{
q=p->next; // q为p的后继
if(q->data==cur_e)
{
pre_e=p->data;
return OK;
}
p=q; // p向后移
}
return INFEASIBLE;
}
Status NextElem(LinkList L,ElemType cur_e,ElemType &next_e)
{ // 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 返回OK;否则操作失败,next_e无定义,返回INFEASIBLE
LinkList p=L->next; // p指向第一个结点
while(p->next) // p所指结点有后继
{
if(p->data==cur_e)
{
next_e=p->next->data;
return OK;
}
p=p->next;
}
return INFEASIBLE;
}
Status ListInsert(LinkList L,int i,ElemType e) //*************
{ // 在带头结点的单链线性表L中第i个位置之前插入元素e
LinkList p = L;
LinkList s;
int j = 0;
while(p && j<i-1){ //寻找第i个结点(j=i-1时,找到第i个结点)
p = p -> next;
++j;
}
if(!p || j>i-1){ //i 值非法
return ERROR;
}
s = (LinkList)malloc(sizeof(LNode)); //新建结点
s -> data = e; //赋值
s -> next = p -> next; //将结点插入链表
p -> next = s; //将结点插入链表
return OK;
}
Status ListDelete(LinkList L,int i,ElemType &e) // 算法2.10。不改变L
{ // 在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
int j=0;
LinkList p=L,q;
while(p->next&&j<i-1) // 寻找第i个结点,并令p指向其前趋
{
p=p->next;
j++;
}
if(!p->next||j>i-1) // 删除位置不合理
return ERROR;
q=p->next; // 删除并释放结点
p->next=q->next;
e=q->data;
free(q);
return OK;
}
Status ListTraverse(LinkList L,void(*vi)(ElemType)) //*************
// vi的形参类型为ElemType,与bo2-1.cpp中相应函数的形参类型ElemType&不同
{ // 初始条件:线性表L已存在
// 操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
LinkList p;
p = L->next; //p指向首元结点
while(p != NULL){ //p非空
vi(p -> data);
p = p -> next;
}
printf("\n");
return OK;
}
- 将上一次实验中的
Merge
函数和main
函数复制到MergeLink.cpp
文件中,将其中的SqList
类型均改为LinkList
类型,就可以运行了
// MergeLink.cpp
typedef int ElemType;// 要加分号
# include "c1.h"
# include "linklist.cpp"
void MergeList_L(LinkList la,LinkList lb,LinkList &lc){
// 已知顺序线性表La和Lb的元素按值非递减排序
// 归并La和Lb得到新的肾虚线性表Lc,Lc的元素也按值非递减排列
InitList(lc);
int i,j,k;
i=j=1;
k=0;
int la_len,lb_len;
la_len = ListLength(la);
lb_len = ListLength(lb);
ElemType ai,bj;
while((i<=la_len)&&(j<=lb_len)){// la与lb均非空
GetElem(la,i,ai);
GetElem(lb,j,bj);
if(ai<=bj){
ListInsert(lc,++k,ai);
++i;
}else{
ListInsert(lc,++k,bj);
++j;
}
}
while(i<=la_len){
GetElem(la,i++,ai);
ListInsert(lc,++k,ai);
}
while(j<=lb_len){
GetElem(lb,j++,bj);
ListInsert(lc,++k,bj);
}
}// MergeList_L
void print(ElemType c){
printf("%d ",c);
}
int main(){
LinkList la,lb,lc;
int j,a[4]={1,3,5,7},b[5]={2,4,6,8,10};
InitList(la);
InitList(lb);
for(j=1;j<=4;j++){
ListInsert(la,j,a[j-1]);
}
printf("la=");
ListTraverse(la,print);
printf("\n");
for(j=1;j<=5;j++){
ListInsert(lb,j,b[j-1]);
}
printf("lb=");
ListTraverse(lb,print);
printf("\n");
MergeList_L(la,lb,lc);
printf("lc=");
ListTraverse(lc,print);
DestroyList(la);
DestroyList(lb);
DestroyList(lc);
return 0;
}
实验结束后,请将源程序( .h
或 .cpp
)和实验报告打包后上传至课程中心的网站
四、问题
请另写一个算法:直接利用指针对链表操作实现两有序表合并
// 问题一.cpp typedef int ElemType;// 要加分号 # include "c1.h" # include "linklist.cpp" void MergeList_L(LinkList la,LinkList lb,LinkList &lc){ // 已知顺序线性表La和Lb的元素按值非递减排序 // 归并La和Lb得到新的肾虚线性表Lc,Lc的元素也按值非递减排列 LinkList pa,pb,pc; pa = la -> next; pb = lb -> next; lc = la; pc = la; while(pa && pb){//pa,pb皆非空 if(pa->data <= pb->data){ pc->next = pa; pc = pa; pa = pa->next; } else{ pc->next = pb; pc = pb; pb = pb->next; } } pc->next = pa?pa:pb;//当la,lb中的一个空时,将未空的一个接入lc free(lb);//释放lb的头结点 }// MergeList_L void print(ElemType c){ printf("%d ",c); } int main(){ LinkList la,lb,lc; int j,a[4]={1,3,5,7},b[5]={2,4,6,8,10}; InitList(la); InitList(lb); for(j=1;j<=4;j++){ ListInsert(la,j,a[j-1]); } printf("la="); ListTraverse(la,print); printf("\n"); for(j=1;j<=5;j++){ ListInsert(lb,j,b[j-1]); } printf("lb="); ListTraverse(lb,print); printf("\n"); MergeList_L(la,lb,lc); printf("lc="); ListTraverse(lc,print); DestroyList(la); DestroyList(lb); DestroyList(lc); return 0; }
认真体会并回答:基本操作集以外的操作通过调用基本操作来实现,而不是直接对顺序表或链表操作,有什么好处?
答:通过调用基本操作来实现,而不是直接对顺序表或链表操作可以提高代码的复用性,从而在成功编写一次代码后不必担心调用的函数会出现错误,从而减少检查量。
五、附件
同实验一
六、发现
两个程序卡死问题
当 ListTraverse
函数为
Status ListTraverse(LinkList L,void(*vi)(ElemType)) //*************
// vi的形参类型为ElemType,与bo2-1.cpp中相应函数的形参类型ElemType&不同
{ // 初始条件:线性表L已存在
// 操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
LinkList p;
p = L; //p指向头结点
while(p != NULL){ //p非空
p = p -> next;
vi(p -> data);
}
printf("\n");
return OK;
}
时,输出结果为
la=1 3 5 7
--------------------------------
Process exited after 5.842 seconds with return value 3221225477
请按任意键继续. . .
而当
while(p != NULL){ //p非空
p = p -> next;
vi(p -> data);
}
更换为
while(p != NULL){ //p非空
vi(p -> data);
p = p -> next;
}
时,输出结果为
la=1 3 5 7
lb=2 4 6 8 10
lc=1 1 1 1 2 2 2 2 2
--------------------------------
Process exited after 0.07741 seconds with return value 0
请按任意键继续. . .
我认为这样的结果出现的原因在于:
出错的函数中的 p = p -> next;
语句在 vi(p -> data);
语句之前,这样就会导致当 p
指向 NULL
时,vi
继续对 NULL -> data
进行操作,导致程序卡死
与此同时,当问题一中 MergeList_L
函数为
void MergeList_L(LinkList la,LinkList lb,LinkList &lc){
// 已知顺序线性表La和Lb的元素按值非递减排序
// 归并La和Lb得到新的肾虚线性表Lc,Lc的元素也按值非递减排列
LinkList pa,pb,pc;
pa = la -> next;
pb = lb -> next;
lc = la;
pc = pa;
while(pa && pb){//pa,pb皆非空
if(pa->data <= pb->data){
pc->next = pa;
pc = pa;
pa = pa->next;
}
else{
pc->next = pb;
pc = pb;
pb = pb->next;
}
}
pc->next = pa?pa:pb;//当la,lb中的一个空时,将未空的一个接入lc
free(lb);//释放lb的头结点
}// MergeList_L
时,输出结果卡死在
la=1 3 5 7
lb=2 4 6 8 10
而当 pc = pa;
调整为 pc = la;
时,输出结果正常:
la=1 3 5 7
lb=2 4 6 8 10
lc=1 2 3 4 5 6 7 8 10
--------------------------------
Process exited after 2.031 seconds with return value 3221226356
请按任意键继续. . .
我认为这样的结果出现的原因在于:
错误程序中的 pc = pa;
使函数 MergeList_L(la,lb,lc);
中无法正常进行
pc->next = pa;
pc = pa;
pa = pa->next;
操作,致使主函数卡在函数 MergeList_L(la,lb,lc);
中无法离开,造成程序卡死
实验三 表达式求值$(2021.10.30)$
一、实验目的
熟悉栈的基本操作及应用。
了解算术表达式求值问题的解决方法。
二、实验内容
利用栈的基本操作实现算术表达式求值
将其扩展到多位数的运算并增加指数运算。
记录实验过程并总结实验经验,完成实验报告。
三、实验步骤
新建一个文件夹,并命名。
将实验材料(附件)中的
c1.h
和sqstack.cpp
文件复制到同一个文件夹中。其中,
c1.h
中包含程序中常用的头文件及宏定义,以后每次都要记得将该文件包括到你的源程序中。
sqstack.cpp
中包含线性表顺序存储结构的定义和部分基本操作的实现。
3.新建一个 caculater.cpp
源文件(主文件)中,在其中输入以下三个命令(语句)及一个空的 main
函数
typedef char SElemType;
#include "c1.h"
#include "sqstack.cpp"
main(){
}
在
caculater.cpp
中实现下列函数:In
,Precede
,Operate
,EvaluateExpression
.请注意:写完一个函数后,应回到进行调用及编译,检查错误。测试正确后,再开始写其它函数。
在
main
函数中调用EvaluateExpression
,编译运行,检查结果是否正确。修改使其能实现多位数的运算,并能进行指数计算。
实验结束后,请将源程序( .h
或 .cpp
)和实验报告打包后上传至课程中心的网站
四、问题
- 运算符栈和操作数栈中的元素应定义为何类型?
答:定义为字符类型($SElemType$)。
- 输入的数字字符如何转换成整型?
答:首先在定义元素变量时定义为整型,再在计算时增加一个数组用于储存数字,并且将之后 $ASCII$ 码的计算改为数值的计算。
- 算法的输入数据、中间结果及最终结果最大为多少?如何使算法能处理任意整数、小数的算术运算?
答:最大为 $9$ ,要任意处理整数或小数,要更改元素类型,并使用数组来储存每一个数值。
- 如果要加入指数运算,如何实现?
答:增加一个对指数计算的优先级的判断。
五、附件
//SqStack.cpp
// 栈的顺序存储表示
#define STACK_INIT_SIZE 10 // 存储空间初始分配量
#define STACKINCREMENT 2 // 存储空间分配增量
struct SqStack
{
SElemType *base; // 在栈构造之前和销毁之后,base的值为NULL
SElemType *top; // 栈顶指针
int stacksize; // 当前已分配的存储空间,以元素为单位
}; // 顺序栈
// 顺序栈的基本操作(9个)
Status InitStack(SqStack &S)
{ // 构造一个空栈S
if(!(S.base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType))))
exit(OVERFLOW); // 存储分配失败
S.top=S.base;
S.stacksize=STACK_INIT_SIZE;
return OK;
}
Status DestroyStack(SqStack &S)
{ // 销毁栈S,S不再存在
free(S.base);
S.base=NULL;
S.top=NULL;
S.stacksize=0;
return OK;
}
Status ClearStack(SqStack &S)
{ // 把S置为空栈
S.top=S.base;
return OK;
}
Status StackEmpty(SqStack S)
{ // 若栈S为空栈,则返回TRUE,否则返回FALSE
if(S.top==S.base)
return TRUE;
else
return FALSE;
}
int StackLength(SqStack S)
{ // 返回S的元素个数,即栈的长度
return S.top-S.base;
}
Status GetTop(SqStack S,SElemType &e)
{ // 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR
if(S.top>S.base)
{
e=*(S.top-1);
return OK;
}
else
return ERROR;
}
Status Push(SqStack &S,SElemType e)
{ // 插入元素e为新的栈顶元素
if(S.top-S.base>=S.stacksize) // 栈满,追加存储空间
{
S.base=(SElemType *)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType));
if(!S.base)
exit(OVERFLOW); // 存储分配失败
S.top=S.base+S.stacksize;
S.stacksize+=STACKINCREMENT;
}
*(S.top)++=e;
return OK;
}
Status Pop(SqStack &S,SElemType &e)
{ // 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
if(S.top==S.base)
return ERROR;
e=*--S.top;
return OK;
}
Status StackTraverse(SqStack S,Status(*visit)(SElemType))
{ // 从栈底到栈顶依次对栈中每个元素调用函数visit()。
// 一旦visit()失败,则操作失败
while(S.top>S.base)
visit(*S.base++);
printf("\n");
return OK;
}
六、发现
return
优化
int Operate(char a,char theta,char b){
// 运算a theta b
a -= 48;// '0'对应的数值为48
b -= 48;
switch(theta){
case'+': return a+b;
case'-': return a-b;
case'*': return a*b;
case'/': if(b==0){
printf("除数b不能为0!");
}
else{
return a/b;
}
}
}//Operate
首先 $return$ 的值需要为字符型,那么函数就需要为 $char$ 型,不能使用 $int$ 型,且 $return$ 值需要加 $48\ (‘0’的ASC码)$
其次我们可以采用全函数只使用一个 $return$ 的方式以简化函数
如下
SElemType Operate(SElemType a,SElemType theta,SElemType b){
// 运算a theta b
SElemType c;
a -= 48;// '0'对应的数值为48
b -= 48;
switch(theta){
case'+': c = a+b+48; break;
case'-': c = a-b+48; break;
case'*': c = a*b+48; break;
case'/': if(b==0){
printf("除数b不能为0!");
}
else{
c = (a/b)+48;
}
break;
}
return c;
}//Operate
return
与 exit
函数的区别
return 返回函数值,是关键字; exit 是一个函数。
return 是语言级别的,它表示了调用堆栈的返回;而 exit 是系统调用级别的,它表示了一个进程的结束。
return 是函数的退出(返回);exit 是进程的退出。
return 是 C 语言提供的,exit 是操作系统提供的(或者函数库中给出的)。
return 用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit 函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给 OS (操作系统),这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非 0 为非正常退出。
非主函数中调用 return 和 exit 效果很明显,但是在 main 函数中调用 return 和 exit 的现象就很模糊,多数情况下现象都是一致的。
atoi
函数
atoi
(表示 ascii to integer
)是把字符串转换成 整型 数的一个函数,应用在计算机程序和办公软件中。 int atoi (const char *nptr)
函数会扫描参数 nptr
字符串,会跳过前面的空白字符(例如空格,tab
缩进)等。 如果 nptr
不能转换成 int
或者 nptr
为空字符串,那么将返回 $0$ 。