开课实验室:计算机科学与工程实验(电子楼) 2020年11月22日
学院 | 计算机科学与网络工程学院 | 年级、专业、班 | 网络工程194 | 姓名 | jwt | 学号 | ||
实验课程 | 数据结构实验 | 成绩 | ||||||
实验项目 | 实验一 线性表、堆栈和队列的 *** 作与实现 | 指导老师 |
|
1、线性表的链表实现:遍历、查找、插入、删除、翻转
2、栈的链式存储结构实现:入栈、出栈
3、队列的链式存储结构的实现:入队、出队
4、线性表、栈和队列的应用实现
二、使用仪器、器材微机一台
*** 作系统:WinXP
编程软件:C++
三、实验内容及原理 1、线性表的链表实现(1) 用随机函数生成10个3位整数(100~999),把这些整数存于链表中;
思路:首先调用initList(List &L)初始化链表,然后用后插法创建链表,数据域赋予随机数,随机数通过srand((unsigned)time(NULL))确定,并规定范围为rand()%900+100即100-999。
(2) 输出链表的内容;
思路:定义指针p指向首元结点,然后向后扫描,逐个数据域输出。
(3) 读入一个整数,查看该整数是否在表中,若在,输出其位置(首位置为1);思路:定义指针p指向首元结点,然后向后扫描,当找到与之相同的数据域时,返回下标。
(4) 读入一个整数,以及要插入的位置,把该整数插入到链表中,输出链表的内容(要求判断输入的位置是否合理);
思路:定义指针p指向首元结点,然后向后扫描,到达相应位置后,建立一个新结点,新结点指针域储存要插入结点的指针域,然后插入该结点后
(5) 读入一个整数,若该整数在链表里,删除该整数,输出链表的内容;
思路:先调用位置判断函数判断链表中是否有该数据,若有则找到该结点,创建一个新结点临时储存该结点,该结点的上一结点指向该结点的下一结点,释放要删除结点的空间。
(6) 把链表的内容翻转,输出链表的内容。
思路:因为用后插法创建了链表,所以可以用前插法再创建个新链表即可实现翻转。
2、栈的链式存储结构实现(1) 用随机函数生成10个3位整数(100~999),把这些整数应用入栈 *** 作存于堆栈中,在入栈接口处设置断点①,按“F5”启动调试,按“F10”逐句执行,直到数据全部入栈。程序暂停时观察栈顶数据和栈顶位置;
思路:与创建单链表相似。
(2) 应用出栈 *** 作输出堆栈的内容,在出栈接口处设置断点②,按“F5”启动调试,按“F10”逐句执行,直到所有数据完全出栈,程序暂停时观察栈顶数据和栈顶位置的变化。
思路:与链表输出类似,栈顶元素要释放空间,并更新栈顶元素指向
3、队列的链式存储结构的实现(1) 用随机函数生成10个3位整数(100~999),把这些整数应用入队 *** 作存于队列中;
思路:与创建链表类似,要更新队尾指针
(2) 应用遍历 *** 作输出队列的内容;
思路:与输出链表类似
(3) 把队列的内容翻转,应用出队 *** 作输出队列的内容。
思路:定义两个新结点临时储存队头空间,先将队头结点指向队尾,然后通过移动新结点把队尾倒数的元素逐个移到前边,实现翻转
4、线性表、栈和队列的应用实现(1) 用随机函数生成10个3位整数(100~999),把这些整数存于单链表中,然后读入一个整数,以该值为基准把单链表分割为两部分,所有小于该值的结点排在大于或等于该值的结点之前。
思路:创建两个新链表,一个用于储存比该数小的数据,一个用于储存比该数大的数据,最后合并两个链表。
(2) 假设一个字符串中可以包含三种括号:( )[ ]{},且这三种括号可以按任意次序嵌套使用(如:“...[...{...}...[...]...]...(...)” 为合法嵌套,“...[...{... )...[...]...]...(...)”为不合法嵌套)。编写判别给定表达式中所含括号是否正确配对出现的算法,如果是合法嵌套则返回为true,如果是不符合法嵌套则返回为false。
思路:运用链栈可以解决,当读取到左括号时,把左括号压入栈,当读取到右括号时判断栈顶是否有相应的左括号与之匹配,若有,则栈顶元素出栈,循环下去即可解决。
(3) 用队列求解迷宫问题的最短路径
思路:
1.从起点开始,将其加入队列,设置距离为0;
2.从队列首端取出位置,将从这个位置能够到达的位置加入队列,并且让这些位置的距离为上一个位置的距离加上1;
3.一轮探索至无法继续向队列中添加元素,即当前所在点的四周情况全部考虑完成后,循环第二步,直到将终点添加到队列中。说明此时已经找到了路径;
4.求出并存储各条路径,并比较得出最短路径
四、源码及结果分析 一、线性表的链表实现//1.
void inputList(List &L) //输入随机数,后插法创建链表
{
List p, r;//p用于生成新结点,r为尾指针
r = L;//尾指针初始化,指向头结点
srand((unsigned)time(NULL));//随机数种子
for (int i=0; i < 10; i++)
{
p = new LNode;//生成一个新结点p
p->data = rand() % 900 + 100;//输入元素值赋给*p的数据域
p->next = NULL;
r->next = p;//将新结点插入尾结点
r = p;//更新尾指针,指向新结点*p
}
cout << "创建成功" << endl;
}
//2.
void outputList(List L) //输出链表元素
{
List p;
p = L->next;//p指向首元结点
while (p!=NULL)//p向后扫描,直到p为空
{
cout << p->data << " ";//输出p的数据域
p = p->next;//p指向下一个节点
}
cout << endl;
}
//3.
int locateList(List L, int num) //判断链表中是否存在该元素,若有则返回相应下标
{
List p=NULL;
p = L->next;//p指向首元结点
int temp = 1;// 首位置为1
while (p != NULL)//p向后扫描,直到p为空
{
if (p->data == num)//查找成功
return temp;//返回相应元素的下标
temp++;
p = p->next;//p指向下一个节点
}
return -1;//找不到元素,返回-1
}
//4.
void insertList(List &L, int num,unsigned int index) //插入元素至链表指定位置,num为插入的元素,index为插入的位置
{
List p = L;
int j = 1;// 首位置为1
while (p && (j < index))//p非空且初始位置小于要插入的指定位置时,向后扫描
{
p = p->next;
j++;
}
if (!p && (j>index))//index大于链表长度或index<1
{
cout << "插入失败!" << endl;
return;
}
else
{
List r = NULL;
r = new LNode;//生成新结点
r->data = num;//将*r的数据域置为num
r->next = p->next;//新结点保存原来结点的下一指向
p->next = r;//要插入位置的指针域指向新结点
cout << "插入成功!" << endl;
}
cout << "插入新元素后的链表为: ";
outputList(L);//输出插入后的链表
if (index != locateList(L, num))//判断是否插入指定位置
cout << "未插入指定位置!" << endl;
else
cout << "已插入指定位置!" << endl;
}
//5.
void deleteList(List &L, int num) //删除链表节点
{
int i=locateList(L, num);//判断链表中是否有该元素,若有,则返回相应下标
if (i == -1)
{
cout << "删除失败,链表中没有该元素!" << endl;
return;
}
else
{
List p=L,q=NULL;
int j = 1;
while ((p->next) && (j < i))//查找第i-1个结点,p指向该结点
{
p = p->next;
j++;
}
if ((!p->next) || (j>i))//当i>链表长度或者i<1时,删除位置不合理
{
cout << "删除位置不合理!" << endl;
return;
}
else
{
q = new LNode;
q = p->next;//q用于临时保存被删除结点的地址以备释放
p->next = q->next;//改变删除节点前驱结点的指针域
delete q;//释放删除结点的空间
cout << "删除成功!" << endl;
}
}
}
//6.
void flipList(List L)//因为创建时用了后插法,所以用前插法创建新链表即可实现翻转
{
List p = NULL,r=NULL,h=NULL;
initList(p);//初始化新链表
r = L->next;
while (r)//原链表向后扫描
{
h = new LNode;//生成新结点
h->data = r->data;//新结点的数据域置为原链表首元结点的数据
r = r->next;//向后扫描
h->next = p->next;
p->next = h;//新结点插入头结点后
}
outputList(p);//输出翻转后的链表
}
创建链表,并赋予10个随机数,然后输出链表,输入666后,选择插入的位置为3,再输出链表,可以看到666已经插在555之后排在第三位;然后删除666,输出删除后的链表,结果正确;最后调用翻转链表函数,翻转成功!
二、栈的链式存储结构实现//1.
void inputStack(LinkStack &S)//链栈的入栈
{
LinkStack p=NULL;
srand((unsigned)time(NULL));//随机数种子
for (int i = 0; i < 10; i++)
{
p = new SNode;//生成新结点
p->data = rand() % 900 + 100;//将数据域置为随机数100-999
p->next = S;//将新结点插入栈顶
S = p;//修改栈顶指针为p
}
cout << "入栈成功" << endl;
}
注:因为10个随机数调试太多,所以换成了2个随机数再进行调试
在入栈处设置断点1,先是955入栈,然后第二次循环到920入栈,栈顶元素变为了920
//2.
void popStack(LinkStack &S)//链栈的出栈
{
while (S)
{
cout << S->data << " ";//输出元素
LinkStack p = S;//p临时保存栈顶元素空间,以备释放
S = S->next;//更新栈顶指针
delete p;//释放原栈顶元素空间
}
cout << endl;
}
初始时栈顶元素为920与上边一致,说明入栈 *** 作正确,出栈时先释放920,下一步释放955,最后栈为空。
三、队列的链式存储结构的实现//1.
void inputQueue(LinkQueue &Q) //创建队列
{
QueuePtr p;
srand((unsigned)time(NULL));
for (int i = 0; i < 10; i++)
{
p = new QNode; //为入队元素分配结点空间,用指针p指向
p->data = rand()%900+100; //赋予随机数
p->next = NULL;
Q.rear->next = p; //将新结点插入到队尾
Q.rear = p; //修改队尾指针
}
}
//2.
void outputQueue(LinkQueue &Q)//输出队列内容
{
QueuePtr p;
p = Q.front->next;//p指向队头元素
while (p)//向后扫描
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
//3.
void flipQueue(LinkQueue &Q)//翻转队列
{
QueuePtr h = Q.front->next;//暂存队头空间
QueuePtr r = Q.front->next;
int count = countQueue(Q);//count用于储存队列长度,countQueue(Q)用于统计队列长度
Q.front->next = Q.rear;//将队头元素置为队尾元素
for (int i = 0; i < count; i++)
{
while (r)//r非空时向后扫描
{
if (r->next == Q.rear)//找到队尾指针(r为倒数第二个结点)
{
Q.rear->next = r;//队尾指针的指针域指向r
Q.rear = r;//更新队尾指针
break;
}
r = r->next;
}
r = h;//将队头空间赋予r,循环后达到翻转效果
}
r->next = NULL;//最后一个元素指针域置为空
Q.rear = r;//更新队尾指针
cout << "翻转后的队列为: ";
outputQueue(Q);
}
先创建队列,并用outputQueue()输出原队列,然后调用翻转函数flipQueue(),翻转成功!
四、线性表、栈和队列的应用实现//1.
List divideList(List L,int num)//分割链表
{
List sh = NULL;//小链表头
List st = NULL;//小链表尾
List lh = NULL;//大链表头
List lt = NULL;//大链表尾
List p = L->next;//p指向原链表的首元结点
while (p)//向后扫描
{
if (p->data < num)//若该结点的数据小于num
{
if (sh == NULL)//sh尚未初始化
{
initList(sh);
sh->next = p;//把该结点插到sh之后
st = sh->next;//更新尾指针st
}
else//若sh已经初始化,已有首元结点
{
st->next = p;//插入该结点
st = st->next;//更新尾指针st
}
}
else//若该结点的数据大于num
{
if (lh == NULL)//lh尚未初始化
{
initList(lh);
lh->next = p;//把该结点插到lh之后
lt = lh->next;//更新尾指针lt
}
else
{
lt->next = p;//插入该结点
lt = lt->next;// 更新尾指针lt
}
}
p = p->next;//指向下一结点
}
if (sh == NULL)//如果没有比num小的数,则小链表为空,返回大链表的头指针
{
return lh;
}
else
st->next = lh->next;//将大链表的首元结点插入小链表的尾部
return sh;//返回小链表的头指针
}
如图,首先创建链表赋予10个随机数,然后输入300,结果分割后的链表显示为,小于300的3个数都在左边,大于300的7个数都在右边
//2.
bool matching()//括号匹配
{
LinkStack S;
initStack(S);//初始化链栈
bool judge = true;//判断匹配结果,初始化为真
char str[20];//字符数组存储算术表达式
cout << "请输入括号组成的字符串: ";
cin >> str;
int length = strlen(str);//获取输入算术表达式的长度
for (int i = 0; i < length; i++)
{
switch (str[i])//根据符号的不同执行不同的操作
{
case '[':
case '(':
case '{':
//读取到左括号时,将左括号压入栈
pushStack(S, str[i]);
break;
case ']'://读取到右括号]
{
if (notEmptyStack(S) && getTop(S) == '[')//判断栈顶元素是否为对应的左括号
{
judge = true;
popStack(S, str[i]);//若匹配,judge取真,栈顶元素出栈
}
else
judge = false;//不匹配,judge取假
break;
}
case ')'://读取到右括号)
{
if (notEmptyStack(S) && getTop(S) == '(')//判断栈顶元素是否为对应的左括号
{
judge = true;
popStack(S, str[i]);//若匹配,judge取真,栈顶元素出栈
}
else
judge = false;//不匹配,judge取假
break;
}
case '}'://读取到右括号}
{
if (notEmptyStack(S) && getTop(S) == '{')//判断栈顶元素是否为对应的左括号
{
judge = true;
popStack(S, str[i]);//若匹配,judge取真,栈顶元素出栈
}
else
judge = false;//不匹配,judge取假
break;
}
default:
break;
}
}
if (!notEmptyStack(S) && judge)//如果栈空并且judge判断为真,则匹配成功
return true;
else
return false;
}
测试了两条表达式,第一条匹配失败,第二条匹配成功
//3.
typedef struct node
{
int x, y; //横纵坐标
int pre; //指向前驱点的坐标
}SqType;
typedef struct
{
int x, y;
}item;
item assignMove(item moveEle[4])//向可移动的4个方向的移动后的坐标变化,从正右方顺时针开始
{
for (int i = 0; i < 8; i++)
{
switch (i)
{
case 0:moveEle[0].x = 1; moveEle[0].y = 0; break;
case 1:moveEle[1].x = 0; moveEle[1].y = 1; break;
case 2:moveEle[2].x = -1; moveEle[2].y = 0; break;
case 3:moveEle[3].x = 0; moveEle[3].y = -1; break;
}
}
return moveEle[4];
}
void printPath(SqType *sq,int count[])
{
int min = 999;//假设最短路径长度为999
int i = 0;
int arr[10];//用于储存各路径到达终点路径长度
for (int k = 0; k < 10; k++)//初始化各路径路径长度为0
arr[k] = 0;
for (int j = 0; j < 10; j++)
{
if (count[j] != 0)//若有路径,则计算路径长度
{
i = count[j];
while (i != -1)
{
i = sq[i].pre;
arr[j]++;//更新路径长度
}
}
if (count[j] == 0)//没有路径则跳出循环
break;
}
int length = 0;
for (int j = 0; j < 10; j++)//找出最短路径
{
if ((arr[j] < min) && (arr[j] != 0))
{
min = count[j];//令min等于最短路径的rear
length = arr[j];
}
}
while (min != -1)//打印路径
{
cout << sq[min].x << "," << sq[min].y << " " << endl;
min = sq[min].pre;
}
cout << "最短路径长度为: " << length << endl;
}
void set(int count[], int n)//储存步数
{
for (int i = 0; i < 10; i++)
{
if (count[i]== 0)
{
count[i] = n;
break;
}
}
}
/*利用队列求最短路径*/
void path()
{ //*((int*)pia3 + i * 4 + j)
item moveEle[4];
moveEle[4] = assignMove(moveEle);
//最外一圈建立“围墙”,防止数据溢出数组范围
int maze[9][10] =
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
1, 0, 1, 1, 1, 0, 0, 1, 0, 1,
1, 0, 0, 1, 0, 1, 0, 0, 0, 1,
1, 1, 0, 1, 0, 1, 1, 0, 1, 1,
1, 0, 0, 1, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
int n = 9, m = 10;//行数和列数
SqType sq[num];//用于记录所经过的坐标
int front, rear;//前驱、后继下标
int x, y, i, j, v;//i,j分别对应新的横纵坐标
sq[0].x = 1;
sq[0].y = 1;//设置起点
sq[0].pre = -1;//已经过的点标记为-1
front = rear = 0;
maze[1][1] = -1; //标记已经过的位置
int count[10];//储存各路径的rear
for (int k = 0; k < 10; k++)
count[k] = 0;
while (front <= rear)
{
//更新x,y的坐标
x = sq[front].x;
y = sq[front].y;
for (v = 0; v<4; v++)//朝4各方向移动后的坐标变化
{
i = x + moveEle[v].x;
j = y + moveEle[v].y;
if (maze[j][i] == 0)//0表示通行,1表示没路走
{
rear++;//若可通行,步数+1
//走下一步的坐标
sq[rear].x = i;
sq[rear].y = j;
sq[rear].pre = front;//记录前驱
maze[j][i] = -1;//已经过的点标记为-1
}
if ((i == m-2) && (j == n-2))//设置终点坐标
set(count, rear);//储存步数
}
front++;
}
cout << "最短路径为: " << endl;
printPath(sq, count);
}
因为输入迷宫太繁琐,所以直接定义了一个迷宫的二维数组。
通过执行path()来求解该迷宫的最短路径,并输出最短路径的行走经过以及最短路径的长度。
五、实验结果及分析总结:1.遍历时要注意,创建的指针是指向头指针还是首元结点,不然可能会遍历不到最后一个元素提前结束,或者达不到想要的效果。
2.插入功能应该先要判断插入位置是否合理,否则可能输出乱码
3.链表合并时,应插入第二个链表的首元结点,如果插入的是头指针,会输出一堆数字。
4.括号匹配时,一定要认真看左右括号是否输入对,否则编译器不会提示你错误,要找半天。。。
5.某些程序出bug时,可以设置断点,逐条语句运行找到问题所在。
实验结果:实验结果基本都运行正确(即截屏所示)。
改进:在执行栈的 *** 作时,应该先写一个函数判断是否为空栈。队列链表类似。
Pass:仅作为实验参考,有些地方可能不够完善,见谅一下。转载请注明出处!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)