数据结构上机


实验一:顺序表$(2021.10.08)$

一、实验目的

熟悉线性表顺序存储结构的类型说明及基本操作的实现

二、实验内容

  1. 利用顺序表的基本操作实现两个有序表合并(教材算法2.2)

  2. 补充顺序表中下列基本操作的定义:CLearList,ListEmpty,LocateElem,ListDelete

  3. 记录实验过程并总结实验经验,完成实验报告。

三、实验步骤

  1. 新建一个文件夹,并在其中新建一个扩展名为.cpp的文件;

  2. 将实验材料(附件)中的 c1.hsqlist.cpp 文件复制到同一个文件夹中。其中,

c1.h 包含程序中常用的头文件及宏定义,以后每次都要记得将该文件包括到你的源程序中。

sqlist.cpp 包含线性表顺序存储结构的定义和部分基本操作的实现。

  1. 在你的 cpp 源文件(主文件)中,输入以下三个命令(语句)
typedef int ElemType
#include "c1.h"
#include "sqlist.cpp"
  1. cpp 主文件中写实现两有序表合并的函数 MergeList

请注意:
写完一个函数后,应立即进行编译。
编译通过后,再开始编写其它函数。

  1. 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)和实验报告打包后上传至课程中心的网站。

四、问题

  1. 如果线性表的元素类型为字符型,应如何修改,请实验之。

    // 问题一.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);
    }
  2. 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);
        }
  3. sqlist.cppCLearList,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 意义
  1. %c,%y这些代表你要输出的数据的数据类型;

  2. %d 表示输出十进制有符号的整数;

  3. %u 十进制无符号整数;

  4. %f 表示输出浮点数;

  5. %s表示输出 字符串;

  6. %c表示输出单个字符;

  7. %p表示输出指针的值;

  8. %e表示输出指数形式的浮点数;

  9. %x, %X 表示输出无符号以十六进制表示的整数;

  10. %0 表示输出无符号以八进制表示的整数;

  11. %g表示输出自动选择合适的表示法。

实验二:链式表$(2021.10.22)$

一、实验目的

熟悉线性表的链式存储结构的类型说明和基本操作定义

二、实验内容

  1. 利用基本操作实现两个有序表合并(教材算法2.2)

  2. 补充未完成的基本操作:InitListDestroyListListLengthListTraverseGetElemListInsert

  3. 记录实验过程并总结实验经验,完成实验报告

三、实验步骤

  1. 新建一个文件夹,并在其中新建一个扩展名为 .cpp 的文件。

  2. 将实验材料(附件)中的 c1.hlinklist.cpp 文件复制到同一个文件夹中。

其中,c1.h 中包含程序中常用的头文件及宏定义,以后每次都要记得将该文件包括到你的源程序中。

linklist.cpp 中包含线性表顺序存储结构的定义和部分基本操作的实现。

  1. 新建一个 MergeLink.cpp 源文件(主文件)中,在其中输入以下三个命令(语句)及一个空的 main 函数
typedef int ElemType;

 #include "c1.h"

 #include "linklist.cpp"

 main()

 {

 }
  1. linklist.cpp 实现两有序表合并所需要的基本操作:InitListDestroyListListTraverseGetElemListInsert

请注意:写完一个函数后,应回到 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;
}
  1. 将上一次实验中的 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 )和实验报告打包后上传至课程中心的网站

四、问题

  1. 请另写一个算法:直接利用指针对链表操作实现两有序表合并

    // 问题一.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;
    }
  1. 认真体会并回答:基本操作集以外的操作通过调用基本操作来实现,而不是直接对顺序表或链表操作,有什么好处?

    答:通过调用基本操作来实现,而不是直接对顺序表或链表操作可以提高代码的复用性,从而在成功编写一次代码后不必担心调用的函数会出现错误,从而减少检查量。

五、附件

同实验一

六、发现

两个程序卡死问题

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)$

一、实验目的

  1. 熟悉栈的基本操作及应用。

  2. 了解算术表达式求值问题的解决方法。

二、实验内容

  1. 利用栈的基本操作实现算术表达式求值

  2. 将其扩展到多位数的运算并增加指数运算。

  3. 记录实验过程并总结实验经验,完成实验报告。

三、实验步骤

  1. 新建一个文件夹,并命名。

  2. 将实验材料(附件)中的 c1.hsqstack.cpp 文件复制到同一个文件夹中。

    其中,c1.h 中包含程序中常用的头文件及宏定义,以后每次都要记得将该文件包括到你的源程序中。

sqstack.cpp 中包含线性表顺序存储结构的定义和部分基本操作的实现。

3.新建一个 caculater.cpp 源文件(主文件)中,在其中输入以下三个命令(语句)及一个空的 main 函数

typedef char SElemType;
#include "c1.h"
#include "sqstack.cpp"
main(){
    
}
  1. caculater.cpp 中实现下列函数:In , Precede , Operate , EvaluateExpression.

    请注意:写完一个函数后,应回到进行调用及编译,检查错误。测试正确后,再开始写其它函数。

  2. main 函数中调用 EvaluateExpression ,编译运行,检查结果是否正确。

  3. 修改使其能实现多位数的运算,并能进行指数计算。

实验结束后,请将源程序( .h.cpp )和实验报告打包后上传至课程中心的网站

四、问题

  1. 运算符栈和操作数栈中的元素应定义为何类型?

答:定义为字符类型($SElemType$)。

  1. 输入的数字字符如何转换成整型?

答:首先在定义元素变量时定义为整型,再在计算时增加一个数组用于储存数字,并且将之后 $ASCII$ 码的计算改为数值的计算。

  1. 算法的输入数据、中间结果及最终结果最大为多少?如何使算法能处理任意整数、小数的算术运算?

答:最大为 $9$ ,要任意处理整数或小数,要更改元素类型,并使用数组来储存每一个数值。

  1. 如果要加入指数运算,如何实现?

答:增加一个对指数计算的优先级的判断。

五、附件

 //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
returnexit 函数的区别

摘自C语言 exit 函数 - C语言零基础入门教程

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$ 。


文章作者: Heart-of-engine
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Heart-of-engine !
打赏
  目录