开课实验室:计算机科学与工程实验(电子楼418B) 2022年11月8日
学院 | 计算机科学与网络工程学院 | 年级、专业、班 | 姓名 | 学号 | ||||
实验课程名称 | 数据结构实验 | 成绩 | ||||||
实验项目名称 | 指导老师 |
一、实验目的
掌握线性的定义及基本操作,用链表实现:遍历、查找、插入、删除、翻转。
二、使用仪器、器材
微机一台
操作系统:
编程软件:
三、实验内容
(1)用随机函数生成10个3位整数(100~999),把这些整数存于链表中;
(2)输出链表的内容;
(3)读入一个整数,查看该整数是否在表中,若在,输出其位置(首位置为1);
(4)读入一个整数,以及要插入的位置,把该整数插入到链表中,输出链表的内容(要求判断输入的位置是否合理);
(5)读入一个整数,若该整数在链表里,删除该整数,输出链表的内容;
(6)把链表的内容翻转,输出链表的内容。
四、实验原理
填入自己的内容(思路或算法流程图或伪代码说明等)
0、随机函数:
鉴于此实验多次需要使用随机函数生成10个3位整数(100~999)来进行链表、链栈、链队的相关操作,于是便可以创建一个rand_number.h文件,并在此文件中声明函数produce_rands,在rand_number.cpp文件中具体实现该函数的方法,该函数用来产生一个一维数组,一维数组中包含10个随机生成的3位整数。这样子在链表、链栈、链队存储结构的实现中直接调用函数,就会方便很多。
生成随机函数需要用上time.h和math.h,定义一个系统时间变量time_t t,srand()用来设置rand()产生随机数时的随机数种子,srand((unsigned)time(&t));由系统时间确定随机序列这样子生成的就不是伪随机数了。
最后通过一个大小为10的一维数组接收随机数就可以了。
1、线性表的链表实现+线性表(单链表)的应用实现:
创建一个LinkList.h文件,在此文件中对单链表的结点类型LinkNode进行声明,并在此文件中声明单链表的基本运算算法,包括:初始化单链表、建立的单链表(头插法)、建立单链表(尾插法)、销毁线性表、判断是否为空表、求单链表的长度、遍历单链表,输出链表的内容、按元素值查找并返回元素所在位置、将元素插入至线性表指定位置、删除线性表中第一个与指定值匹配的数据元素等,再声明翻转链表的内容算法ListReverse、判断输入是否为整数算法JudgeInteger、以某整数为基准把单链表分割为两部分的算法ListPartition。
创建一个LinkList.cpp文件,在此文件中对LinkList.h文件中声明的函数进行具体实现。
创建一个main.cpp文件,包含LinkList.h和LinkList.cpp,在此文件中进行单链表存储结构以及单链表的应用实现(以某整数为基准把单链表分割为两部分)。
翻转链表的内容算法ListReverse:
创建了两个指针p、q,分别指向链表的首结点,断裂头结点,当q不指向空时,循环遍历数据结点,并采用头插法插入到头结点之后,这样子就实现了链表内容的翻转。
判断输入是否为整数算法JudgeInteger:
应实验要求,很多内容都需要读入一个整数才能进行相关的链表操作,这时就需要判断读入的数据是否为整数,所以需要设计判断输入是否为整数的算法。
将输入数据设置为浮点数类型double num,若num-(int)==0,说明输入数据为整数。
以某整数为基准把单链表分割为两部分的算法ListPartition:
在函数ListPartition中创建两个链表LessHead、GreatHead,分别存放小于x的节点和大于等于x的节点,将链表中的元素分别进行尾插至两链表,用指针cur遍历完链表L后,将链表LessHead的尾连接GreatHead的头,将GreatHead的尾置为空,最后将L的next指向LessHead的next即可。特别需要注意的是,要在函数体内销毁生成的两个链表头!
2、栈的链式存储结构实现+栈的应用实现(判别给定表达式中所含括号是否正确配对):
创建一个LinkStack.h文件,在此文件中对链栈中的结点类型LinkStNode进行声明,并在此文件中声明链栈的基本运算算法,包括:初始化链栈、释放链栈、判链栈空、入栈、出栈、取栈顶等,再声明判别给定表达式中所含括号是否正确配对的算法Match。
创建一个LinkStack.cpp文件,在此文件中对LinkStack.h文件中声明的函数进行具体实现。
创建一个main.cpp文件,包含LinkStack.h和LinkStack.cpp,在此文件中进行栈的链式存储结构以及链栈的应用实现(判别给定表达式中所含括号是否正确配对)。
特别需要注意的是,由于要求产生的三位随机数是int类型的,而表达式是char类型,故链栈的结点类型LinkStNode、链栈的基本算法函数、判别给定表达式中所含括号是否正确配对的算法函数Match都应设置为模板,即声明结构体模板和函数模板,这样子才具有较强的适应性,以满足不同的数据类型。
在判别给定表达式中所含括号是否正确配对的算法Match中应用入栈与出栈的操作,当字符为左括号时,将其入栈;当字符为右括号时,将栈顶元素(当且仅当此元素为左括号时)出栈。这样子就可以很好地进行括号的匹配。
3、队列的链式存储结构的实现
创建一个LinkQueue.h文件,在此文件中对链队数据结点的类型DataNode进行声明,对链队头结点的类型LinkQuNode进行声明,并在此文件中声明链队的基本运算算法,包括:初始化带头结点的链队、销毁链队、判断链队是否为空、入队、出队、遍历队列、等,再声明翻转链队内容算法ReverseQueue。
创建一个LinkQueue.cpp文件,在此文件中对LinkQueue.h文件中声明的函数进行具体实现。
创建一个main.cpp文件,包含LinkQueue.h和LinkQueue.cpp,在此文件中进行队列的链式存储结构的实现。
翻转链队内容算法ReverseQueue:
判断链队是否为空,链队为空时输出无法翻转,链队不为空时执行翻转操作。
创建三个指针,分别指向前三个数据元素,将尾指针指向首个数据结点(翻转后的尾结点),循环遍历链队中的元素,在循环过程中将后一个数据结点指向前一个数据结点,再将三个指针同步后移,直至循环结束。此时除了倒数两个数据结点没有翻转,其他结点均完成了翻转。将最后一个数据结点指向倒数第二个数据节点,所有数据结点翻转完毕。最后将头结点指向尾结点(翻转后的首结点),将首结点(翻转后的尾结点)的next域置为空,即可完成链队内容的全部翻转。
4、用队列求解迷宫问题的最短路径
创建一个SequentialQueue.h文件,在此文件中对方块类型Box进行声明,对顺序队类型QuType进行声明,并在此文件中声明顺序队的基本运算算法,包括:初始化队列、销毁队列、判断队列是否为空、进队列、出队列,搜索迷宫的路径、输出迷宫的最短路径等,
创建一个SequentialQueue.cpp文件,在此文件中对SequentialQueue.h文件中声明的函数进行具体实现。
创建一个main.cpp文件,包含SequentialQueue.h和SequentialQueue.cpp,在此文件中进行求解迷宫问题的最短路径实现。
五、实验过程原始数据记录
1、实验源代码及注释
(0)随机函数:
rand_number.h
#pragma once
#include "time.h" //时间函数
#include "math.h" //随机函数
#include"iostream"
using namespace std;
void produce_rands(int array[]);
rand_number.cpp
#include"rand_number.h"
void produce_rands(int array[])
{
time_t t; //定义时间变量
srand((unsigned)time(&t)); //由时间确定随机序列,执行一次
for (int i = 0; i < 10; i++)
{
//int rand_n = (1 + rand() % 999);
//while (rand_n < 100) //随机数小于100时,重新生成一个随机数,直到随机数的范围在100-999之间才跳出循环
//{
// rand_n = (1 + rand() % 999);
//}
int rand_n = (100 + rand() % 900); //rand()随机生成一个0~32767的整数,使用%操作,筛选随机数的值
array[i] = rand_n; //将随机数放在数组array[]里面
}
}
rand_number.cpp
#include"rand_number.h"
void produce_rands(int array[])
{
time_t t; //定义时间变量
srand((unsigned)time(&t)); //由时间确定随机序列,执行一次
for (int i = 0; i < 10; i++)
{
//int rand_n = (1 + rand() % 999);
//while (rand_n < 100) //随机数小于100时,重新生成一个随机数,直到随机数的范围在100-999之间才跳出循环
//{
// rand_n = (1 + rand() % 999);
//}
int rand_n = (100 + rand() % 900); //rand()随机生成一个0~32767的整数,使用%操作,筛选随机数的值
array[i] = rand_n; //将随机数放在数组array[]里面
}
}
(1)线性表的链表实现+线性表(单链表)的应用实现:
LinkList.h
#pragma once
#include<iostream>
using namespace std;
typedef int ElemType; //通用类型标识符:ElemType,这里假设ElemType为int类型
typedef struct LNode //结构体这里使用typedef的作用是:给LNode结构体取一个别名:LinkNode
{
ElemType data; //存放元素值
struct LNode* next; //指向后继节点的指针
}LinkNode; //单链表结点类型
//初始化单链表
bool InitList(LinkNode*& L);
//建立的单链表(头插法)
void CreateListF(LinkNode*& L, ElemType a[], int n);
//建立单链表(尾插法)
void CreateListR(LinkNode*& L, ElemType a[], int n);
//销毁线性表
void DestroyList(LinkNode*& L);
//判断是否为空表
bool ListEmpty(LinkNode* L);
//求单链表的长度
int ListLength(LinkNode* L);
//遍历单链表,输出链表的内容
void TraverseList(LinkNode* L);
//按元素值查找并返回元素所在位置
int LocateElem(LinkNode* L, ElemType e);
//将元素插入至线性表指定位置
bool ListInsert(LinkNode*& L, int position, ElemType e);
//删除线性表中第一个与指定值匹配的数据元素
bool ListDelete(LinkNode*& L, ElemType e);
//翻转链表的内容
void ListReverse(LinkNode*& L);
//判断输入是否为整数
bool JudgeInteger(double num);
//以某整数为基准把单链表分割为两部分
void ListPartition(LinkNode*& L,int num);
LinkList.cpp
#include "LinkList.h"
//初始化单链表
bool InitList(LinkNode*& L) //需要对链表进行修改,所有需要加上引用
{
L = (LinkNode*)malloc(sizeof(LinkNode)); //malloc 则必须由我们计算字节数,并且在返回后强行转换为实际类型的指针。
if (L) //判断是否分配了内存空间
{
L->next = NULL; //错误:将NULL写成了Null,程序报错,原因:
return 1;
}
else //未成功分配内存空间,输出“初始化错误”
return 0;
}
//建立的单链表(头插法)
void CreateListF(LinkNode*& L, ElemType a[], int n)
{
LinkNode* s;
L = (LinkNode*)malloc(sizeof(LinkNode));
L->next = NULL;
for (int i = 0; i < n; i++)
{
s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = a[i];
s->next = L->next;
L->next = s;
}
}
//建立单链表(尾插法)
void CreateListR(LinkNode*& L, ElemType a[], int n)
{
LinkNode* s, * r;
L = (LinkNode*)malloc(sizeof(LinkNode)); //创建头结点
r = L; //r始终指向尾结点,初始时指向头结点
for (int i = 0; i < n; i++) //循环建立数据结点
{
s = (LinkNode*)malloc(sizeof(LinkNode)); //创建数据结点s
s->data = a[i]; //给数据结点s的data域赋值
r->next = s; //将结点s插入到结点r之后
r = s; //把r指向尾结点
}
r->next = NULL; //将尾结点r的next域置为NULL
}
//销毁线性表
void DestroyList(LinkNode*& L) //需要对链表进行修改,所有需要加上引用
{
LinkNode* pre = L; //pre指向头结点
LinkNode* p = L->next; //p指向首结点
while (p != NULL) //遍历单链表L
{
free(pre); //释放pre结点
pre = p; //pre和p同时向后移动一个结点
p = pre->next;
}
free(pre); //循环结束,p为NULL,pre指向尾结点,还需释放它
}
//判断是否为空表
bool ListEmpty(LinkNode* L)
{
return(L->next == NULL); //单链表中没有数据,则是空表
}
//求单链表的长度
int ListLength(LinkNode* L)
{
int n = 0;
LinkNode* p = L; //p指向头结点,n置为0(即头结点的序号为0)
while (p->next != NULL)
{
n++;
p = p->next;
}
return n; //循环结束,p指向为尾结点,其序号n即为结点的个数
}
//遍历单链表,输出链表的内容
void TraverseList(LinkNode* L)
{
LinkNode* p = L->next; //p指向首结点
while (p != NULL) //p未指向空前,输出p结点的值
{
cout << p->data << " ";
p = p->next;
}
cout << endl; //循环结束,p指向空
}
//按元素值查找并返回元素所在位置
int LocateElem(LinkNode* L, ElemType e)
{
int i = 1;
LinkNode* p = L->next; //p指向首结点,i置为1(即首结点的序号为1)
while (p != NULL && p->data != e)
{
p = p->next; //往后找
i++;
}
if (p == NULL) //不存在值为e的结点,返回0
return 0;
else //存在值为e的结点,返回其逻辑序号i
return i;
}
//将元素插入至线性表指定位置
bool ListInsert(LinkNode*& L, int position, ElemType e) //position表示插入到哪个结点处;需要修改链表,则需要加上链表的引用
{
int j = 0;
LinkNode* p = L, * s;
if (position <= 0) //结点位置应>0
return 0;
while (j < position - 1 && p != NULL) //当p指向指定结点位置的前一个位置时或p指向空时跳出循环
{
j++;
p = p->next;
}
if (p == NULL) //表中不存在该位置
return 0;
else //找到了第position-1个结点,
{
s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = e; //创建结点s并将其data域置为e
s->next = p->next; //将结点s插入在结点p之后
p->next = s;
return 1;
}
}
//删除线性表中与指定值匹配的数据元素 存疑:若表中不止一个元素与给定元素相同,怎么办?
bool ListDelete(LinkNode*& L, ElemType e) //需要修改链表,则需要加上链表的引用
{
LinkNode* p = L; //p指向首结点
LinkNode* q = p->next; //q指向p的下一个结点
while (q->data != e) //while (q->data != e && q != NULL)错误:最终q==NULL时,语句q->data!=e出错,此时q无data域
{
p = q; //往后找
q = p->next;
if (q == NULL)
break;
}
if (q == NULL) //表中不存在元素e
return 0;
else //找到了元素e
{
p->next = q->next;
free(q);
return 1;
}
}
//翻转链表的内容
void ListReverse(LinkNode*& L) //需要修改链表,则需要加上链表的引用
{
LinkNode* p, * q; //定义两个指针
p = q = L->next; //初始时都指向首结点
L->next = NULL; //断裂头结点,使其next域指向空
while (q != NULL)
{
q = q->next; //q指针后移
p->next = L->next; //经典头插法
L->next = p;
p = q;
}
}
//判断输入是否为整数
bool JudgeInteger(double num)
{
if (num - (int)num == 0)
return true;
else
return false;
}
//以某整数为基准把单链表分割为两部分
void ListPartition(LinkNode*& L, int num)
{
LinkNode* LessHead, * GreatHead, * LessRear, * GreatRear,* cur; //声明表头指针、链表尾指针、链表指针cur
InitList(LessHead); //初始化头结点
InitList(GreatHead); //初始化头结点
LessRear = LessHead; //尾指针始终指向尾结点,初始时指向头结点
GreatRear = GreatHead; //尾指针始终指向尾结点,初始时指向头结点
cur = L->next; //指针cur初始时指向首结点
while (cur != NULL) //指针不指向空时循环,指向空时跳出循环
{
if (cur->data < num)
{
LessRear->next = cur;
LessRear = cur;
}
else
{
GreatRear->next = cur;
GreatRear = cur;
}
cur = cur->next; //指针cur后移
}
LessRear->next = GreatHead->next; //将链表LessHead的尾部连接至链表GreatHead的首结点
GreatRear->next = NULL; //Carefully!连接完两个链表后需要将链表尾结点的next域置为空
L->next = LessHead->next; //获取链表
LessHead->next = NULL; //(写错了:一开始没有这样子写)断裂头结点LessHead
GreatHead->next = NULL; //(写错了:一开始没有这样子写)断裂头结点GreatHead
DestroyList(LessHead); //摧毁链表(表头)LessHead
DestroyList(GreatHead); //摧毁链表(表头)GreatHead
//carefully:若是没有断裂头结点而直接摧毁链表,会使得链表L没有内容,此时链表的next域还不为空,随后调用链表L的遍历时就会程序中断报错!
//方法二:直接释放头结点
//free(LessHead)
//free(GreatHead)
}
Main.cpp
#include"rand_number.h"
#include"LinkList.h"
int main()
{
//链表
int array1[10], array2[10];
double element1, element2, element3;
double position1; //元素插入至链表的位置(逻辑序号)
bool flag1 = 0, flag2 = 0; //标记为零,循环输入
LinkNode* L1, * L2;
cout << "*******线性表(单链表)的实现*******" << endl;
produce_rands(array1);
cout << "随机生成的10个三位整数分别为:" << endl;
for (int i = 0; i < 10; i++)
{
cout << array1[i] << " ";
}
cout << endl << endl;
if (InitList(L1))
cout << "单链表初始化成功" << endl;
else
cout << "单链表初始化错误" << endl;
CreateListR(L1, array1, (sizeof(array1) / sizeof(int)));
cout << endl;
cout << "链表的内容如下:" << endl;
TraverseList(L1);
cout << endl;
cout << "请输入一个整数以查看该数是否在链表中:" << endl;
cin >> element1;
while (JudgeInteger(element1) == 0)
{
cout << "输入数值不是整数,请重新输入:" << endl;
cin >> element1;
}
cout << "输入整数" << element1 << "成功,";
if (LocateElem(L1, int(element1)))
{
cout << "整数" << element1 << "在链表中,其位置(逻辑序号)为:" << endl << LocateElem(L1, element1) << endl;
}
else
{
cout << "整数" << element1 << "不在链表中" << endl;
}
cout << endl;
cout << "请输入要插入到链表的整数:" << endl;
cin >> element2;
while (JudgeInteger(element2) == 0)
{
cout << "输入数值不是整数,请重新输入:" << endl;
cin >> element2;
}
cout << "输入整数" << element2 << "成功" << endl;
while (flag1 == 0)
{
cout << "请输入要插入到链表的位置:" << endl;
cin >> position1;
while (JudgeInteger(position1) == 0)
{
cout << "输入位置不是整数,请重新输入:" << endl;
cin >> position1;
}
if (ListInsert(L1, position1, int(element2))) //可插入的指定位置范围为:[1,11]
{
cout << "将整数" << element2 << "插入到链表的指定位置" << position1 << "后,新链表的内容如下:" << endl;
TraverseList(L1);
flag1 = 1; //成功输入,将标记设为1,跳出循环
}
else
cout << "输入的位置" << position1 << "不合理,重新输入!" << endl;
}
cout << endl;
while (flag2 == 0)
{
cout << "请输入一个整数以删除链表中的该数:" << endl;
cin >> element3;
while (JudgeInteger(element3) == 0)
{
cout << "输入数值不是整数,请重新输入:" << endl;
cin >> element3;
}
cout << "输入整数" << element3 << "成功,";
if (ListDelete(L1, element3)) //写错了:写成了ListDelete==之类的,但是其实ListDelete是个函数,需要先调用函数才会产生返回值
{
cout << "将整数" << element3 << "从链表中删去后,新链表的内容如下:" << endl;
TraverseList(L1);
flag2 = 1; //成功输入,将标记设为1,跳出循环
}
else
cout << "链表中不存在整数" << element3 <<",重新输入!" << endl;
}
cout << endl;
ListReverse(L1);
cout << "翻转链表后,新链表的内容为:" << endl;
TraverseList(L1);
cout << endl;
//链表的应用
cout << "*******线性表(单链表)的应用实现*******" << endl;
produce_rands(array2);
cout << "随机生成的10个三位整数分别为:" << endl;
for (int i = 0; i < 10; i++)
{
cout << array2[i] << " ";
}
cout << endl << endl;
if (InitList(L2))
cout << "单链表初始化成功" << endl;
else
cout << "单链表初始化错误" << endl;
CreateListR(L2, array2, (sizeof(array2) / sizeof(int)));
cout << endl;
cout << "链表的内容如下:" << endl;
TraverseList(L2);
cout << endl;
double element4 = 0;
cout << "请输入一个整数以将链表分割成两部分(所有小于该值的结点排在大于或等于该值的结点之前):" << endl;
cin >> element4;
while (JudgeInteger(element4) == 0)
{
cout << "输入数值不是整数,请重新输入:" << endl;
cin >> element4;
}
cout << "输入整数" << element4 << "成功" << endl;
cout << endl;
ListPartition(L2, int(element4));
cout << "分割后新链表的内容如下:" << endl;
TraverseList(L2);
}
(2)栈的链式存储结构实现+栈的应用实现
LinkStack.h
LinkStack.h
#pragma once
#include<iostream>
using namespace std;
template<typename T>
struct linknode //链栈结点的类型
{
T data; //数据域
struct linknode<T>* next; //指针域
};
//初始化链栈
template<typename T>
bool InitStack(linknode<T>*& s);
//释放链栈
template<typename T>
void DestroyStack(linknode<T>*& s);
//判链栈空
template<typename T>
bool StackEmpty(linknode<T>* s);
//入栈
template<typename T>
bool Push(linknode<T>*& s, T e);
//出栈
template<typename T>
bool Pop(linknode<T>*& s, T& e);
//取栈顶
template<typename T>
bool GetTop(linknode<T>* s, T& e);
//判别给定表达式中所含括号是否正确匹配
template<typename T>
bool Match(linknode<T>*& s,char exp[],int n);
LinkStack.cpp
#include"LinkStack.h"
//初始化空链栈
template<typename T>
bool InitStack(linknode<T>*& s) //创建一个空链栈s(创建链栈的头结点)
{
s = (linknode<T>*)malloc(sizeof(linknode<T>)); //为s分配内存空间
if (s == NULL) //分配空间不成功
{
return 0;
}
else //分配空间成功
{
s->next = NULL; //将头结点的next域置为空
return 1;
}
}
//释放链栈
template<typename T>
void DestroyStack(linknode<T>*& s) //释放链栈s所有结点占用的空间
{
linknode<T>* pre = s; //声明两个结点指针,
linknode<T>* p = s->next; //其中pre指向头结点,p指向首结点
while (p != NULL) //遍历单链表L
{
free(pre); //释放pre结点
pre = p; //pre和p同步后移
p = pre->next;
}
free(pre); //此时pre指向尾结点,释放其空间
}
//判链栈空
template<typename T>
bool StackEmpty(linknode<T>* s)
{
return(s->next == NULL); //头结点的next域为空,则链栈为空
}
//入栈
template<typename T>
bool Push(linknode<T>*& s, T e)
{
linknode<T>* p;
p = (linknode<T>*)malloc(sizeof(linknode<T>)); //新建结点p并为它分配空间
if (p == NULL)
{
cout << "分配空间不成功,数据无法入栈" << endl;
return false;
}
p->data = e; //将元素e存放在新结点p中
p->next = s->next; //经典头插法
s->next = p; //将p结点插入作为首结点
return true;
}
//出栈
template<typename T>
bool Pop(linknode<T>*& s, T& e)
{
linknode<T>* p; //创建链栈指针p
if (s->next == NULL) //链栈为空,返回false
{
cout << "链栈为空,无法出栈" << endl;
return false;
}
p = s->next; //将指针p指向首结点
e = p->data; //提取首结点的数据
s->next = p->next; //从链栈中删除首结点
free(p); //释放被删结点(首结点)的存储空间
return true;
}
//取栈顶
template<typename T>
bool GetTop(linknode<T>* s, T& e)
{
if (s->next == NULL) //链栈为空,返回false
return false;
e = s->next->data; //提取首结点的数据
return true;
}
//判别给定表达式中所含括号是否正确匹配
template<typename T>
bool Match(linknode<T>*& s, char exp[], int n)
{
int i = 0;
char e;
bool match = true;
while (i < n && match) //当遍历完表达式或出现了不匹配的括号时跳出循环
{
if (exp[i] == '(') //当前字符为左括号,将其入栈
Push(s, exp[i]);
else if (exp[i] == ')') //当前字符为右括号
{
if (GetTop(s, e) == true) //成功取栈顶元素e
{
if (e != '(') //栈顶元素不为'('时
match = false; //不匹配,赋值假
else //栈顶元素为'('时
Pop(s, e); //将栈顶元素出栈
}
else //无法取栈顶元素时表示不匹配:表达式读取到右括号时,链栈中已无左括号与其匹配
match = false;
}
i++; //继续处理其它字符
}
if (!StackEmpty(s)) //链栈不为空,链栈中还存在剩余的括号(左括号)没有出栈,说明表达式括号不匹配
match = false;
return match;
}
Main.cpp
#include"rand_number.h"
#include"LinkStack.cpp"
int main()
{
//栈的链式存储结构的实现
cout << "**********栈的链式存储结构的实现**********" << endl;
int array[10];
int element1 = 0;
bool flag = 1; //flag用来判断所有数据是否完全出栈
produce_rands(array); //用随机函数生成10个3位整(100~999)
cout << "随机生成的10个三位整数分别为:" << endl;
for (int i = 0; i < 10; i++)
{
cout << array[i] << " ";
}
cout << endl;
cout << endl;
typedef struct linknode<int> LinkStNode;
LinkStNode* s;
if (InitStack(s))
cout << "链栈初始化成功" << endl;
else
cout << "链栈初始化错误" << endl;
cout << endl;
cout << "执行入栈操作:" << endl;
for (int i = 0; i < (sizeof(array) / sizeof(int)); i++)
{
if (Push(s, array[i])) //把这些整数应用入栈操作存于链栈中
cout << "第" << i + 1 << "个元素" << "入栈成功" << endl;
else
cout << "第" << i + 1 << "个元素" << "入栈失败" << endl;
}
cout << endl;
cout << "执行出栈操作,出栈内容如下:" << endl;
for (int i = 0; i < (sizeof(array) / sizeof(int)); i++)
{
if (Pop(s, element1))
{
cout << element1 << " "; //应用出栈操作输出堆栈的内容
}
else
{
flag = 0; //链栈为空,将flag设为0
cout << "链栈为空,无法出栈" << endl;
break;
}
}
cout << endl;
if (flag == 1) //链栈为1,说明所有数据均已出栈
cout << "所有数据已完全出栈" << endl;
cout << endl;
DestroyStack(s);
cout << "链栈销毁成功" << endl;
cout << endl;
//链栈的应用实现
cout << "**********链栈的应用实现**********" << endl;
char exp1[] = "(56-20)/(4+2)";
char exp2[] ="((100-80)*(90/30))";
char exp3[] = "2+(333+(80-40)";
char exp4[] = "(90-80)/(7+3)+(9/3)*3)";
typedef struct linknode<char> LinkStNode2;
LinkStNode2* st;
if (InitStack(st))
cout << "链栈初始化成功" << endl;
else
cout << "链栈初始化错误" << endl;
cout << "给定表达式1:";
for (int i = 0; i < sizeof(exp1) / sizeof(char); i++) //表达式的遍历
{
cout << exp1[i];
}
if (Match(st,exp1, sizeof(exp1) / sizeof(char)))
cout << "所含括号合法嵌套" << endl;
else
cout<<"所含括号不合法嵌套"<<endl;
DestroyStack(st);
cout << "链栈销毁成功" << endl;
cout << endl;
if (InitStack(st))
cout << "链栈初始化成功" << endl;
else
cout << "链栈初始化错误" << endl;
cout << "给定表达式2:";
for (int i = 0; i < sizeof(exp2) / sizeof(char); i++) //表达式的遍历
{
cout << exp2[i];
}
if (Match(st, exp2, sizeof(exp2) / sizeof(char)))
cout << "所含括号合法嵌套" << endl;
else
cout << "所含括号不合法嵌套" << endl;
DestroyStack(st);
cout << "链栈销毁成功" << endl;
cout << endl;
if (InitStack(st))
cout << "链栈初始化成功" << endl;
else
cout << "链栈初始化错误" << endl;
cout << "给定表达式3:";
for (int i = 0; i < sizeof(exp3) / sizeof(char); i++) //表达式的遍历
{
cout << exp3[i];
}
if (Match(st, exp3, sizeof(exp3) / sizeof(char)))
cout << "所含括号合法嵌套" << endl;
else
cout << "所含括号不合法嵌套" << endl;
DestroyStack(st);
cout << "链栈销毁成功" << endl;
cout << endl;
if (InitStack(st))
cout << "链栈初始化成功" << endl;
else
cout << "链栈初始化错误" << endl;
cout << "给定表达式4:";
for (int i = 0; i < sizeof(exp4) / sizeof(char); i++) //表达式的遍历
{
cout << exp4[i];
}
if (Match(st, exp4, sizeof(exp4) / sizeof(char)))
cout << "所含括号合法嵌套" << endl;
else
cout << "所含括号不合法嵌套" << endl;
DestroyStack(st);
cout << "链栈销毁成功" << endl;
cout << endl;
}
(3)队列的链式存储结构的实现
LinkQueue.h
#pragma once
#include<iostream>
using namespace std;
typedef int ElemType; //通用类型标识符:ElemType,这里假设ElemType为int类型
typedef struct qnode
{
ElemType data; //存放元素
struct qnode* next; //指向下一个结点的指针
}DataNode; //链队数据结点的类型
typedef struct
{
DataNode* front; //指向队首结点
DataNode* rear; //指向队尾结点
}LinkQuNode; //链队头结点的类型
//初始化带头结点的链队
bool InitQueue(LinkQuNode*& q);
//销毁链队
void DestroyQueue(LinkQuNode*& q);
//判断链队是否为空
bool QueueEmpty(LinkQuNode* q);
//入队
bool EnQueue(LinkQuNode*& q, ElemType e);
//出队
bool DeQueue(LinkQuNode*& q, ElemType &e);
//取队头元素
int GetFront(LinkQuNode &q, ElemType &e);
//遍历队列
void TraverseQueue(LinkQuNode* q);
//翻转链队内容
void ReverseQueue(LinkQuNode*& q);
LinkQueue.cpp
#include "LinkQueue.h"
//初始化带头结点的链队
bool InitQueue(LinkQuNode*& q) //需要对链表进行修改,所有需要加上引用
{
q = (LinkQuNode*)malloc(sizeof(LinkQuNode)); //为q分配内存空间
if (q) //分配空间成功
{
q->front = q->rear = NULL; //将链队头结点的front和rear域置为空
cout << "链队初始化成功" << endl;
return 1;
}
else //分配空间不成功
{
cout << "链队初始化错误" << endl; //提示初始化错误
return 0;
}
}
//销毁链队
void DestroyQueue(LinkQuNode*& q)
{
DataNode* pre = q->front, * p; //创建数据结点指针pre和q
if (pre != NULL)
{
p = pre->next; //p指向pre结点的后继结点
while (p != NULL) //p不空时循环
{
free(pre); //释放pre结点
pre = p; //pre、p同步后移
p = p->next;
}
free(pre); //释放最后一个数据结点
}
free(q); //释放链队头结点
}
//判断链队是否为空
bool QueueEmpty(LinkQuNode* q)
{
return(q->rear == NULL); //链队空的条件:头结点的rear域指向空(front域指向空)
}
//入队
bool EnQueue(LinkQuNode*& q, ElemType e)
{
DataNode* p;
p = (DataNode*)malloc(sizeof(DataNode)); //创建一个新的数据结点p
p->data = e;
p->next = NULL;
if (q->rear == NULL) //若链队为空,则新的数据结点既是首结点又是尾结点
q->front = q->rear = p; //头尾指针均指向新结点
else //若链队不为空
{
q->rear->next = p; //将新结点链入队尾并将rear指针指向它
q->rear = p; //注意:此时入队只需改变尾指针
}
return 1;
}
//出队
bool DeQueue(LinkQuNode*& q, ElemType& e)
{
DataNode* t;
if (q->rear == NULL) //原来链队为空,无法出队,返回0
return 0;
t = q->front; //将t指向出队的结点(首个数据结点)
if (q->front == q->rear) //若链队中只有一个数据结点时
q->front = q->rear = NULL; //需要将头尾指针指向空
else //若链队中有两个或两个以上的结点时
q->front = q->front->next; //注意:此时出队只需改变头指针(将头指针指向下一个数据结点)
e = t->data; //读取出出队结点(首结点)的数据
free(t); //释放数据结点t
return 1;
}
//遍历队列
void TraverseQueue(LinkQuNode* q)
{
//ElemType e;
DataNode* p = q->front; //指针q指向首个数据结点
if (QueueEmpty(q))
cout << "链队为空,无法遍历" << endl;
else
{
//while (DeQueue(q, e)) //错误:使用出队的操作来进行
//{ //后果:遍历后会使链队为空,而遍历不应改变链队
// cout << e << " ";
//}
while (p!=NULL) //从链队的首个数据结点开始循环输出,直至p指向空
{
cout << p->data << " ";
p = p->next; //p指针后移
}
cout << endl;
}
}
//翻转链队内容
void ReverseQueue(LinkQuNode*& q)
{
if (QueueEmpty(q))
cout << "链队为空,无法翻转" << endl;
else //写错了,没有用else扩起来后面的内容,这样子的话,执行完if语句之后还会接着执行后面的语句。比较bool类型的函数,返回false后就不会执行后面的语句了,所以不用else括起来后后面的语句也没有关系
{
DataNode* pre = q->front; //创建三个指针,分别指向前三个数据元素
DataNode* pcur = pre->next;
DataNode* pnext = pcur->next;
q->rear = pre; //将尾指针指向首个数据结点(翻转后的尾结点)
while (pnext) //循环结束后,除了倒数两个数据结点没有翻转,其他结点均完成了翻转
{
pcur->next = pre; //将后一个数据结点指向前一个数据结点
pre = pcur; //将三个指针同步后移
pcur = pnext;
pnext = pnext->next;
}
pcur->next = pre; //将最后一个数据结点指向倒数第二个数据节点,所有数据结点翻转完毕
q->front = pcur; //将头结点指向尾结点(翻转后的首结点)
q->rear->next = NULL; //易漏:需要将首结点(翻转后的尾结点)的next域置为空
cout << "翻转成功" << endl;
}
}
Main.cpp
//链队
int array[10];
int element1 = 0;
produce_rands(array); //用随机函数生成10个3位整数(100~999)
cout << "随机生成的10个三位整数分别为:" << endl;
for (int i = 0; i < 10; i++)
{
cout << array[i]<<" ";
}
cout << endl;
cout << endl;
LinkQuNode* q;
InitQueue(q);
cout << endl;
cout << "判断此时链队是否为空:" << endl;
if (QueueEmpty)
cout << "链队为空" << endl;
else
cout << "链队不为空" << endl;
cout << endl;
cout <<"对链队进行遍历操作:" << endl;
TraverseQueue(q);
cout << endl;
cout << "对链队进行翻转操作:" << endl;
ReverseQueue(q);
cout << endl;
for (int i = 0; i < (sizeof(array) / sizeof(int)); i++)
{
EnQueue(q, array[i]); //把这些整数应用入队操作存于链列中
}
cout << "将随机数存入链队后,链队的内容如下(使用遍历操作进行输出):" << endl;
TraverseQueue(q); //应用遍历操作输出链列的内容
if (QueueEmpty(q))
cout << "此时链队为空" << endl;
else
cout << "此时链队不为空" << endl;
cout << endl;
cout << "将链队的内容进行翻转,新链队的内容如下(使用出队操作进行输出):" << endl;
ReverseQueue(q); //把链队的内容翻转
while (DeQueue(q, element1)) //应用出队操作输出链队的内容
{
cout << element1 << " ";
}
cout << endl;
if (QueueEmpty(q))
cout << "此时链队为空" << endl;
else
cout << "此时链队不为空" << endl;
(4)用队列求解迷宫问题的最短路径
SequentialQueue.h
#pragma once
#include<iostream>
using namespace std;
#define MaxSize 50
//使用一个顺序队qu保存走过的方块,该队列的类型声明如下:
typedef struct
{
int i, j; //方块的位置:i为行号,j为列号
int pre; //本路径中上一个方块在队列中的下标
}Box; //方块类型
typedef struct
{
Box data[MaxSize]; //存放队中元素
int front, rear; //队头指针和队尾指针
}QuType; //顺序队类型
//初始化队列
void InitQueue(QuType*& q);
//销毁队列
void DestroyQueue(QuType*& q);
//判断队列是否为空
bool QueueEmty(QuType* q);
//进队列
bool enQueue(QuType*& q, Box e);
//出队列
bool deQueue(QuType*& q, Box& e);
//搜索迷宫的路径
bool mgpath1(int mg[][10], int xi, int yi, int xe, int ye);
//输出迷宫的最短路径
void dispapath(QuType* qu, int front);
SequentialQueue.cpp
#include"SequentialQueue.h"
//初始化队列
void InitQueue(QuType*& q) //构造一个空队列q
{
q = (QuType*)malloc(sizeof(QuType)); //申请内存空间
if (q != NULL) //内存空间申请成功
q->front = q->rear = -1; //头尾指针初始状态均为-1
else //内存空间申请失败
cout << "顺序队列初始化错误" << endl; //输出初始化错误
}
//销毁队列
void DestroyQueue(QuType*& q) //释放队列q占用的存储空间
{
free(q);
}
//判断队列是否为空
bool QueueEmty(QuType* q)
{
return(q->front == q->rear == -1); //头尾指针均为-1,队列为空
}
//进队列
bool enQueue(QuType*& q, Box e)
{
if (q->rear == MaxSize - 1) //队满上溢出
return false; //返回假
q->rear++; //队尾加一
q->data[q->rear] = e; //在rear位置插入元素e
return true; //返回真
}
//出队列
bool deQueue(QuType*& q, Box& e)
{
if (q->front == q->rear) //队满下溢出
return false;
q->front++;
e = q->data[q->front];
return true;
}
bool mgpath1(int mg[][10],int xi, int yi, int xe, int ye) //搜索路径为(xi,yi)->(xe,ye)
{
Box e;
int i, j, di, i1, j1;
QuType* qu; //定义顺序队指针qu
InitQueue(qu); //初始化队列qu
e.i = xi; e.j = yi; e.pre = -1;
enQueue(qu, e); //(xi,yi)进队
mg[xi][yi] = -1; //标记:将入队后的元素赋值为-1,避免重复搜索
while (!QueueEmty(qu)) /队不为空时循环
{
deQueue(qu, e); //出队方块e,非环形队列中元素e仍在队列中
i = e.i; j = e.j;
if (i == xe && j == ye) //找到了出口,输出路径
{
dispapath(qu, qu->front); //调用函数dispapath输出路径
DestroyQueue(qu); //销毁队列
return true; //找到一条路径(最短路径)时返回真
}
for (di = 0; di < 4; di++) //循环遍历每个方位,把每个相邻可走的方块进队
{
switch (di)
{
case 0:i1 = i - 1; j1 = j; break;
case 1:i1 = i; j1 = j + 1; break;
case 2:i1 = i + 1; j1 = j; break;
case 3:i1 = i; j1 = j - 1; break;
}
if (mg[i1][j1] == 0)
{
e.i = i1; e.j = j1;
e.pre = qu->front; //指向路径中上一个方块的下标
enQueue(qu, e); //(i1,j1)方块进队
mg[i1][j1] = -1; //将其赋值为-1,避免重复搜索
}
}
}
DestroyQueue(qu); //销毁队列
return false; //未找到任何路径时返回假
}
void dispapath(QuType* qu, int front) //从队列qu中找到一条迷宫路径并输出
{
Box path[MaxSize];
int p = front, k = 0, i;
while (p != -1) //搜索反向路径path[0..k-1]
{
path[k++] = qu->data[p];
p = qu->data[p].pre;
}
printf("一条迷宫路径如下:\n");
for (i = k - 1; i >= 0; i--) //反向输出path构成正向路径
{
printf("\t(%d,%d)", path[i].i, path[i].j); //写错了,写成了/t
if ((k - i) % 5 == 0) //每输出5个方块后换一行
printf("\n");
}
printf("\n");
}
Main.cpp
#include"SequentialQueue.h"
#include"rand_array.h"
int main()
{
cout << "********固定的迷宫********" << endl;
const int M = 8;
const int N = 8;
//表示迷宫的数组mg,每个元素表示一个方块的状态:为0时表示对应的方块是通道,为1时表示对应方块是障碍物(不可走)
int mg[M+2][N+2] = //M与N必须为常量,否则会报错
{
{1,1,1,1,1,1,1,1,1,1},{1,0,0,1,0,0,0,1,0,1},
{1,0,0,1,0,0,0,1,0,1},{1,0,0,0,0,1,1,0,0,1},
{1,0,1,1,1,0,0,0,0,1},{1,0,0,0,1,0,0,0,0,1},
{1,0,1,0,0,0,1,0,0,1},{1,0,1,1,1,0,1,1,0,1},
{1,1,0,0,0,0,0,0,0,1},{1,1,1,1,1,1,1,1,1,1} };
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
cout << mg[i][j] << " ";
if (j == 9)
cout << endl;
}
}
if (!mgpath1(mg,1, 1, M, N))
printf("该迷宫问题没有解!");
//cout << "********随机生成的迷宫********" << endl;
//int map[10][10];
//produce_rand_map(map);
//for (int i = 0; i < 10; i++)
//{
// for (int j = 0; j < 10; j++)
// {
// cout << map[i][j] << " ";
// if (j == 9)
// cout << endl;
// }
//}
//if (!mgpath1(map, 1, 1, 10, 10))
// printf("该迷宫问题没有解!");
return 1;
}
2、实验过程中的错误及原因总结
(0)随机函数
1、一开始没有使用种子,这样子导致每次产生的随机数都相同,即伪随机数。解决办法:设置种子,由系统时间确定随机序列这样子生成的就不是伪随机数了。
(1)线性表的链表实现+线性表(单链表)的应用实现:
1、NULL写错了
原因:实验课前很少亲自动手打数据结构的代码,写成了null,程序提示报错
2、误以为将某整数插入到链表中的插入合理位置为1-10,实际上是1-11
原因:没有设断点进行调试,不明确链表插入元素的边界条件
3、删除链表元素函数遍历链表的循环体的边界条件设置出错
原因:最终指针指向空时已经没有data域了,再使用q->data!=e出错,此时q无data域就会报错
4、创建结点与创建节点指针写错了
原因:刚开始对链栈的理解出了问题,其实在main.cpp中应该声明的是头结点指针
5、对头结点指针重定义
原因:线性表的链表实现与线性表(单链表)的应用实现写在一起后,要注意区分二者的头结点,不可重复定义同名的头结点指针
6、一开始没有判断输入的数是否为整数,若是不判断,则在调用后续函数时会发生异常
(2)栈的链式存储结构实现+栈的应用实现
1、一开始没有将链栈的结点类型LinkStNode、链栈的基本算法函数、判别给定表达式中所含括号是否正确配对的算法函数Match设置为模板,即声明结构体模板和函数模板。这样子不具有较强的适应性,不能满足不同的数据类型。
原因:没有养成写模板的习惯,模板这一块熟练度较薄弱。
解决方法:定义结构体模板和函数模板
总结:以后写程序多试试使用模板,提高熟练度,提高代码的强适应性。
2、使用模板时:在.h文件里声明,在.cpp文件里定义,然后在main函数里包含.h文件,出现了链接报错!
原因:函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件只有声明,没有定义,那么编译器无法实例化该模板,最终导致连接错误(类模板同样!!!)
解决方法:在main函数里包含实现了模板定义的文件。即#include"LinkStack.cpp"
3、编写判别给定表达式中所含括号是否正确配对的算法时,把链栈的声明、初始化、销毁写在函数体中时,会报错。而将其写在main函数中时不会报错。未找到原因(应该与结构体模板和函数模板有关)。
(3)队列的链式存储结构的实现
1、在返回值为void类型的函数体中使用if,else语句出错了。
原因:没有用else扩起来后面的内容,这样子的话,执行完if语句之后还会接着执行后面的语句。比较:bool类型的函数,返回false后就不会执行后面的语句了,所以不用else括起来后面的语句也没有关系。
(4)用队列求解迷宫问题的最短路径
1、二维数组mg[M][N],会出现报错,因为数组中的M,N必须为常量
解决方法:将其设置为常量
六、实验结果及分析
截屏的实验结果和结果分析
1、线性表的链表实现+线性表(单链表)的应用实现:
实验结果截屏:
实验结果分析:
- 使用头插法后,链表内各元素的顺序与数组的顺序相反;使用尾插法后,链表内各元素的顺序与数组的顺序相同。
- 以某整数为基准把单链表分割为两部分的函数,只进行分割两部分的操作,所有小于该整数的元素的相对位置并未发生改变,所有大于该整数的元素的相对位置也未发生改变。
对该结果的评价:
1、此程序的结果是正确的
2、改进之处:我做的删除线性表中与指定值匹配的数据元素的函数只能满足删除首个与匹配值相同的元素。但是删除链表指定整数的操作有可能删掉不止一个元素,例如:链表中若是出现了2个888,那么读入删除整数888后,应该删掉链表中的所有888,而非第一个888。
2、栈的链式存储结构实现+栈的应用实现(判别给定表达式中所含括号是否正确配对)
实验结果截屏:
实验结果分析:
1、链栈只能在栈顶进行插入或删除操作,栈的特点是“先进后出”,即后进栈的元素先出栈
2、使用链栈来判别给定表达式中所含括号是否正确配对,充分应用了链栈“先进后出”的特点
对该结果的评价:
1、此程序的结果是正确的
2、改进之处:若是可以在判别给定表达式中所含括号是否正确配对的算法中把链栈的声明、初始化、销毁写在函数体中,程序会更清晰。
3、队列的链式存储结构的实现
实验结果截屏:
实验结果分析:
1、因为有着队首结点指针和队尾结点指针,所以链队的插入和删除操作可以分别在各自的一端操作,即队尾插入(入队),队首删除(出队)。所以每个元素都按照进入的次序出队,即先进先出。
2、使用遍历操作进行链队内容输出后,链表内容不变;使用出队操作进行链表内容输出后,链表内容发生改变,链表为空。
对该结果的评价:
1、此程序的结果是正确的
2、此程序考虑到了遍历操作与出队操作进行链表内容输出的不同,并判断了链表内容输出后链表是否为空
3、未发现可改进之处
4、用队列求解迷宫问题的最短路径
实验结果截屏:
实验结果分析:
1、使用顺序队的方式存储迷宫路径,反向输出path中所有方块位置构成的一条正向迷宫路径
2、显然,得到的迷宫路径为最优解,也就是最短路径
对该结果的评价:
1、此程序的结果是正确的
2、改进之处:此程序还在调试随机生成迷宫并输出该迷宫的最短路径的方法,若是可以写出来,则该程序的适应性将增强很多