目录
写在前面
课程采用教材《数据结构(C语言版)》严蔚敏,吴伟民,清华大学出版社。
本系列博文用于自我学习总结和期末复习使用,同时也希望能够帮助到有需要的同学。如果有知识性的错误,麻烦评论指出。
抽象数据类型(ADT)
ADT定义
抽象数据类型定义的格式:
ADT 抽象数据类型名{
数据对象:<数据对象的定义>
数据关系:<数据关系的定义>
基本操作:<基本操作的定义>
}ADT 抽象数据类型名
其中,数据对象和数据关系的定义用伪码描述,基本操作的定义格式为:
基本操作名(参数表)
初始条件:<初始条件描述>
操作结果:<操作结果描述>
基本操作有两种参数:赋值参数只为操作提供输入值;引用参数以&打头,除可提供输入值外,还将返回操作结果。“初始条件”描述了操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息。“操作结果”说明了操作正常完成之后,数据结构的变化状况和应返回的结果。若初始条件为空,则省略之。
ADT表示和实现
需要载入一些函数库,预定义常量和类型,数据结构的表示(存储结构)用类型定义(typedef)描述,基本操作的算法采用函数描述,赋值语句,选择语句,循环语句,结束语句,输入输出语句,注释,基本函数,逻辑运算。
特别说明函数描述的形式:
函数类型 函数名(函数参数表){
// 算法说明
语句序列
} // 函数名
三元组Triplet的定义与实现
定义ADT
ADT Triplet{
数据对象:D = {e1,e2,e3 | e1,e2,e3∈ElemSet(定义了关系运算的某个集合)}
数据关系:R1={<e1,e2>,<e2,e3>}(包含三个元素的三元组,可用数组表示)
基本操作:
InitTriplet(&T,v1,v2,v3)
操作结果:构造了三元组T,元素e1,e2,e3分别被赋以v1,v2,v3的值。
DestroyTriplet(&T)
操作结果:三元组T被销毁。
Get(T,i,&e)
初始条件:三元组T已存在,1≤i≤3。
操作结果:用e返回T的第i元的值。
Put(&T,i,e)
初始条件:三元组T已存在,1≤i≤3。
操作结果:改变T的第i元的值为e。
IsAscending(T)
初始条件:三元组T已存在。
操作结果:如果T的3个元素按升序排列则返回1,否则返回0。
IsDescending(T)
初始条件:三元组T已存在。
操作结果:如果T的3个元素按降序排列,则返回1,否则返回0。
Max(T,&e)
初始条件:三元组T已存在。
操作结果:用e返回T的3个元素中最大值。
Min(T,&e)
初始条件:三元组T已存在。
操作结果:用e返回T的3个元素中最小值。
}ADT Triplet
今后定义ADT每行起始不再增加缩进和空格,请读者自行脑补。Ψ( ̄∀ ̄)Ψ
实现三元组Triplet
用到的函数库、头文件和命名空间
#include <iostream>
using namespace std;
#include <stdio.h>
#include <malloc.h>
函数执行结果状态代码
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status是函数的类型,其值是函数结果状况代码
typedef int Status;
typedef int ElemType;//将ElemType定义为int类型
采用的存储结构
//-----采用动态分配的顺序存储结构-----
typedef ElemType* Triplet;//由IniTriplet函数分配3个元素存储空间
基本操作的函数原型说明
//-----基本操作的函数原型说明-----
Status IniTriplet(Triplet& T, ElemType v1, ElemType v2, ElemType v3);
// 操作结果:构造了三元组T,元素e1,e2,e3分别被赋以v1,v2,v3的值。
Status DestoryTriplet(Triplet& T);
// 操作结果:三元组T被销毁。
Status Get(Triplet& T, int i, ElemType& e);
// 初始条件:三元组T已存在,1≤i≤3。
// 操作结果:用e返回T的第i元的值。
Status Put(Triplet& T, int i, ElemType e);
// 初始条件:三元组T已存在,1≤i≤3。
// 操作结果:改变T的第i元的值为e。
Status IsAscending(Triplet T);
// 初始条件:三元组T已存在。
// 操作结果:如果T的3个元素按升序排列,则返回1,否则返回0。
Status IsDescending(Triplet T);
// 初始条件:三元组T已存在。
// 操作结果:如果T的3个元素按降序排列,则返回1,否则返回0。
Status Max(Triplet T, ElemType& e);
// 初始条件:三元组T已存在。
// 操作结果:用e返回T的3个元素中最大值。
Status Min(Triplet T, ElemType& e);
// 初始条件:三元组T已存在。
// 操作结果:用e返回T的3个元素中最小值。
void PrintT(Triplet T);
// 初始条件:三元组T已存在。
// 操作结果:输出三元组T中的元素。
基本操作的实现
//-----基本操作的实现-----
Status IniTriplet(Triplet& T, ElemType v1, ElemType v2, ElemType v3){
// 构造三元组T,依次置T的3个元素初值为v1,v2,v3。
T = (ElemType*)malloc(3 * sizeof(ElemType)); // 分配3个元素的存储空间
if (!T) exit(OVERFLOW);// 分配存储空间失败
T[0] = v1; T[1] = v2; T[2] = v3;
return OK;
}// IniTriplet
Status DestoryTriplet(Triplet& T){
// 销毁三元组T。
free(T);
T = NULL;
return OK;
}// DestoryTriplet
Status Get(Triplet& T, int i, ElemType& e){
// 1≤i≤3,用e返回T的第i元的值。
if ((i < 1 || i>3) || T == NULL)return INFEASIBLE;
e = T[i - 1];
return OK;
}// Get
Status Put(Triplet& T, int i, ElemType e){
// 1≤i≤3,置T的第i元的值为e。
if ((i < 1 || i>3) || T == NULL)return INFEASIBLE;
T[i - 1] = e;
return OK;
}// Put
Status IsAscending(Triplet T){
// 如果T的3个元素按升序排列,则返回1,否则返回0。
if (T == NULL)return INFEASIBLE;
return (T[0] <= T[1] && T[1] <= T[2]);
}// IsAscending
Status IsDescending(Triplet T) {
// 如果T的3个元素按降序排列,则返回1,否则返回0。
if (T == NULL)return INFEASIBLE;
return (T[0] >= T[1] && T[1] >= T[2]);
}// IsDescending
Status Max(Triplet T, ElemType& e){
// 用e返回指向T的最大元素的值。
if (T == NULL)return INFEASIBLE;
e = (T[0] >= T[1]) ? (T[0] >= T[2] ? T[2] : T[1]) : (T[1] >= T[2] ? T[1] : T[2]);
return OK;
}// Max
Status Min(Triplet T, ElemType& e) {
// 用e返回指向T的最小元素的值。
if (T == NULL)return INFEASIBLE;
e = (T[0] <= T[1]) ? (T[0] <= T[2] ? T[0] : T[2]) : (T[1] <= T[2] ? T[1] : T[2]);
return OK;
}// Min
void PrintT(Triplet T){
// 顺序输出三元组T中的元素。
if (T != NULL) cout << "T中元素有:" << T[0] << " " << T[1] << " " << T[2] << endl;
else cout << "三元组不存在!\n";
}// PrintT
编写主函数
int main()
{
Triplet T;
int e;
Status flag;// 状态标志
// 构造三元组T,并赋值
flag = IniTriplet(T, 90, 95, 100);
PrintT(T);
// 返回T的第2元的值
flag = Get(T, 2, e);
if (flag > 0) cout << "T中第2个元素为:" << e << endl;
else cout << "ErrorType:" << flag << endl;
// 置T的第3元的值为80
flag = Put(T, 3, 80);
if(flag > 0) PrintT(T);
else cout << "ErrorType:" << flag << endl;
// 判断T的3个元素是否按升序排列,是则返回1,否则返回0。
flag = IsAscending(T);
if (flag > 0) cout << "T中字符是升序。\n";
else if (IsDescending(T) > 0)// 判断T的3个元素是否按降序排列,是则返回1,否则返回0。
cout << "T中字符是降序。\n";
else if (flag < 0)
cout << "ErrorType:" << flag << endl;
else cout << "T中字符无序。\n";
// 获取T的最大元素的值。
flag = Max(T, e);
if (flag > 0) cout << "T中最大的元素为:" << e << endl;
else cout << "ErrorType:" << flag << endl;
// 获取T的最小元素的值。
flag = Min(T, e);
if (flag > 0) cout << "T中最小的元素为:" << e << endl;
else cout << "ErrorType:" << flag << endl;
// 销毁三元组T
DestoryTriplet(T);
PrintT(T);// 验证三元组已销毁
return 0;
}
测试结果
顺序表List(创建,销毁,重置,插入,删除)
顺序表List定义
1、线性结构特点
线性结构的特点是:在数据元素的非空有限集中,(1)存在唯一的一个被称做“第一个”的数据元素;(2)存在唯一的一个被称做“最后一个”的数据元素; (3)除第一个之外,集合中的每个数据元素均只有一个前驱;(4)除最后一个之外,集合中每个数据元素均只有一个后继。
2、线性表的类型定义
线性表是最常用的且最简单的一种数据结构,它的长度可根据需要增长或缩短,对线性表的数据元素不仅可以进行访问,还可以进行插入和删除等。
本次实验中抽象数据类型线性表的定义如下:
ADT List{
数据对象:D={ai| ai∈ElemSet,i=1,2,…,n,n≥0}
数据关系:R1={<ai-1,ai>| ai-1,ai ∈D,i=1,2,…,n }
基本操作:
InitList(&L)
操作结果:构造一个空的线性表L。
DestroyList(&L)
初始条件:线性表L已存在。
操作结果:销毁线性表L。
ClearList(&L)
初始条件:线性表L已存在。
操作结果:将L重置为空表。
ListInsert(&L,i,e)
初始条件:线性表L已存在,1≤i≤ListLength(L)+1。
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1。
ListDelete(&L,i,&e)
初始条件:线性表L已存在且非空,1≤i≤ListLength(L)。
操作结果:删除L第i个数据元素,并用e返回其值,L的长度减1。
PrintList(L)
初始条件:线性表L已存在。
操作结果:输出线性表信息。
}ADT List
3、线性表的顺序表示和实现
顺序存储:把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。
顺序存储的线性表的特点:(1)线性表的逻辑顺序与物理顺序一致;(2)数据元素之间的关系是以元素在计算机内“物理位置相邻”来体现。
这里采用数组来描述数据结构中的顺序存储结构,并采用malloc函数申请空间,来实现顺序表的长度可变。
顺序表List实现
//1、用到的库函数、头文件和命名空间
#include <iostream>
using namespace std;
#include <stdio.h>
#include <malloc.h>
//2、函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status是函数的类型,其值是函数结果状况代码
typedef int Status;
typedef int ElemType;//将ElemType定义为int类型
//3、采用的存储结构
//-----线性表的动态分配顺序存储结构-----
#define LIST_INIT_SIZE 100// 线性表存储空间的初始分配量
#define LISTINCREMENT 10// 线性表存储空间的分配增量
typedef struct {
ElemType* elem;// 存储空间基址
int length;// 当前长度
int listsize;// 当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;
//4、基本操作的函数原型说明及实现
//-----基本操作的函数原型说明-----
Status InitList(SqList& L);
// 操作结果:构造一个空的线性表L。
Status DestoryList(SqList& L);
// 初始条件:线性表L已存在。
// 操作结果:销毁线性表L。
Status ListInsert(SqList& L, int i, ElemType e);
// 初始条件:线性表L已存在,1≤i≤ListLength(L)+1。
// 操作结果:在线性表L中第i个位置之前插入新的元素e。
Status ListDelete(SqList& L, int i, ElemType& e);
// 初始条件:线性表L已存在且非空,1≤i≤ListLength(L)。
// 操作结果:在顺序线性表中删除第i个元素,并用e返回其值
void PrintList(SqList L);
// 初始条件:线性表L已存在。
// 操作结果:输出线性表信息。
Status ClearList(SqList& L);
// 初始条件:线性表L已存在。
// 操作结果:将L重置为空表。
Status ListEmpty(SqList L);
// 初始条件:线性表L已存在。
// 操作结果:若L为空表,则返回TURE,否则返回FALSE。
Status InitList(SqList& L) {
// 构造一个空的线性表L。
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;
}// IntiList
Status DestoryList(SqList& L) {
// 销毁线性表L。
if (!L.elem)return INFEASIBLE;// 判断elem是否已空
free(L.elem);// 销毁线性表
L.elem = NULL;// 指针指空
return OK;
}
Status ListInsert(SqList& L, int i, ElemType e) {
// 在顺序线性表L中第i个位置之前插入新的元素e。
// i的合法值为1≤i≤ListLength(L)+1。
if (i<1 || i>L.length+1 || L.elem == NULL)return ERROR;// 参数合法
if (L.length == L.listsize)// 当前存储空间已满,增加分配
{
L.listsize += LISTINCREMENT;// 增加存储容量
ElemType* elem_tmp;// 临时存放原空间元素
elem_tmp = (ElemType*)malloc(sizeof(ElemType) * L.listsize);
if (!elem_tmp)exit(OVERFLOW);// 存储空间分配失败
for (int j = 0; j < L.length; j++)// 向扩容后的空间复制原空间元素
elem_tmp[j] = L.elem[j];
free(L.elem);// 释放原空间
L.elem = elem_tmp;// 完成扩容
}
for (int j = L.length; j >= i-1 ; j--)// 插入位置之后的元素右移
L.elem[j] = L.elem[j - 1];
L.elem[i - 1] = e;// 插入e
L.length++;// 表长加1
return OK;
}// ListInsert
Status ListDelete(SqList& L, int i, ElemType& e) {
// 在顺序线性表中删除第i个元素,并用e返回其值
// i的合法值为1≤i≤ListLength(L)
if (i<1 || i>L.length || L.elem == NULL || L.length == 0)return ERROR;// 不合法情况
e = L.elem[i - 1];// 被删除元素赋给e
for (int j = i-1; j < L.length-1; j++)// 被删除元素之后的元素左移
L.elem[j] = L.elem[j + 1];
L.length--;// 表长减1
return OK;
}// ListDelet
void PrintList(SqList L) {
// 输出线性表信息
if (!L.elem)exit(INFEASIBLE);// 判断elem是否已空
cout << "线性表元素有:";
for (int i = 0; i < L.length; i++)
cout << L.elem[i] << " ";
cout << endl << "线性表长度:" << L.length
<< endl << "线性表容量:" << L.listsize << endl << endl;
}// PrintList
Status ClearList(SqList& L) {
// 重置线性表L。
if (!L.elem)return INFEASIBLE;// 判断elem是否已空
free(L.elem);// 释放指针
if (InitList(L))return OK;
}
Status ListEmpty(SqList L) {
if (!L.elem)return INFEASIBLE;// 判断elem是否已空
if (L.length == 0)return TRUE;
return FALSE;
}
//5、主函数
int main()
{
// 初始化测试
SqList L;
InitList(L);
PrintList(L);
// 插入测试
for (int i = 1; i <= 100; i++)// i=1时测试表头插入,
ListInsert(L, i , i );// i>1时测试表尾连续插入LIST_INIT_SIZE-1个元素
PrintList(L);
ListInsert(L, 101, 101);// 测试扩容
PrintList(L);
ListInsert(L, 89, 0);// 测试中间位置插入
PrintList(L);
// 删除测试
ElemType e;
ListDelete(L, 89, e);// 测试中间位置删除
PrintList(L);
cout << "删除的元素为:" << e << endl << endl;
ListDelete(L, 1, e);// 测试表头位置删除
PrintList(L);
cout << "删除的元素为:" << e << endl << endl;
ListDelete(L, L.length, e);// 测试表尾位置删除
PrintList(L);
cout << "删除的元素为:" << e << endl << endl;
// 重置测试
ClearList(L);
PrintList(L);
// 销毁测试
DestoryList(L);
PrintList(L);// 表销毁会以INFEASIBLE值退出
return 0;
}
测试结果
实验总结
本次实验实现了线性表的顺序存储结构,以及顺序表的创建,插入,删除,重置,销毁功能。
创建时,采用malloc函数申请LIST_INIT_SIZE * sizeof(ElemType)个空间,并使L.length = 0,L.listsize = LIST_INIT_SIZE,完成顺序表L的初始化。销毁时,释放L.elem并使其指空,再对顺序表L操作时,L.elem为空,不可操作。重置时,如果L.elem不空,就再调用一次初始化函数InitList(L),重置顺序表L。
插入时,满足初始条件后,先判断顺序表长度L.length与容量L.listsize是否相等,一般情况下,不会出现长度大于容量的情况,故判断是否等于即可。如果等于,则表明当前容量已满,需要再申请LISTINCREMENT个空间。具体操作是:
(1)先扩充容量,L.listsize += LISTINCREMENT
(2)用临时指针elem_tmp指向比原来多LISTINCREMENT个空间的首地址
(3)将原空间的数据元素复制到新申请的空间中,这里采用for循环,一次一个赋值。
(4)释放原空间,让L.elem指向存有原数据元素的新空间。
之后,先将插入位置以后的元素都往后移一位,把插入数据元素放入插入位置,最后L.length++。
删除时,将删除位置的元素先通过e传出,再把删除位置之后的元素依次往前移一位,执行删除操作前的最后一位元素和已占用的多余空间可以不用处理。如果需要处理,可以采用插入时申请新空间一样的方法,不过新空间要比原来的空间少LISTINCREMENT。最后L.length--。
最后采用for循环逐个输出顺序表中的数据元素及当前元素个数和存储容量。
通过本次实验,可以明显感受到对顺序表执行插入和删除操作有诸多的不便,占用额外的空间,插入删除操作不考虑改变存储空间时,时间复杂度为O(n)。但是采用顺序存储结构,可以与逻辑结构更好地对应,便于理解。