此实验为数据结构课程的第二个实验 栈和队列操作及其应用
-
一、实验目的
1.掌握栈和队列的存储表示和实现
2.掌握栈和队列的基本操作实现。
3.掌握栈和队列在解决实际问题中的应用。
-
二、实验要求
问题描述:某银行有一个客户办理业务站,在单位时间内随机地有客户到达,设每位客户的业务办理时间是某个范围的随机值。设只有一个窗口,一位业务人员,要求程序模拟统计在设定时间内,业务人员的总空闲时间和客户的平均等待时间。假定模拟数据已按客户到达的先后顺序依次存于某个文本数据文件中,对应每位客户有两个数据:到达时间和需要办理业务的时间。
用队列 每个数据元素包含两个值 遍历队列 获取每个客户的值
-
三、主要功能函数参考
程序中主要包含下面几个功能函数:
void initstack():初始化堆栈
int Make_str():语法检查
int push_operate(int operate):将操作码压入堆栈
int push_num(double num):将操作数压入堆栈
int procede(int operate):处理操作码
int change_opnd(int operate):将字符型操作码转换成优先级
int push_opnd(int operate):将操作码压入堆栈
int pop_opnd():将操作码弹出堆栈
int caculate(int cur_opnd):简单计算+,-,*,/
double pop_num():弹出操作数
-
四、实验任务
认真阅读与理解实验内容的具体要求,参考教材相关章节,结合实验内容的要求,编写实验程序并上机调试与测试,完成实验报告的撰写。
博主:"哈哈要开始自己搞代码了!对于刚开始接触实验课的友友是不是无从下手!让我来解救你的实验课吧!"
我们需要用到 :dev-c++ Anaconda python环境
-
以下是我们的操作过程
-
以下是我们用到的代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 客户信息结构体
typedef struct {
int arrivalTime; // 到达时间
int serviceTime; // 办理业务时间
} Customer;
// 简单的队列实现
typedef struct {
Customer *array;
int front, rear, capacity, count;
} Queue;
// 初始化队列
Queue* initQueue(int capacity) {
Queue *q = (Queue*)malloc(sizeof(Queue));
q->capacity = capacity;
q->front = q->rear = 0;
q->count = 0;
q->array = (Customer*)malloc(capacity * sizeof(Customer));
return q;
}
// 入队操作
bool enqueue(Queue *q, Customer data) {
if (q->count == q->capacity)
return false;
q->array[q->rear] = data;
q->rear = (q->rear + 1) % q->capacity;
q->count++;
return true;
}
// 出队操作
bool dequeue(Queue *q, Customer *data) {
if (q->count == 0)
return false;
*data = q->array[q->front];
q->front = (q->front + 1) % q->capacity;
q->count--;
return true;
}
int main() {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
printf("Failed to open file.\n");
return -1;
}
Queue *queue = initQueue(100); // 假设最大客户数量为100
int currentTime = 0, totalWaitTime = 0, idleTime = 0, servedCount = 0;
Customer customer, lastServed;
while (fscanf(file, "%d %d", &customer.arrivalTime, &customer.serviceTime) != EOF) {
enqueue(queue, customer);
}
fclose(file);
// 模拟过程
while (dequeue(queue, &customer)) {
// 更新当前时间到下一个客户到达的时间
currentTime = customer.arrivalTime > currentTime ? customer.arrivalTime : currentTime;
// 计算空闲时间
if (servedCount > 0) { // 不是第一个客户
idleTime += currentTime - lastServed.arrivalTime - lastServed.serviceTime;
}
// 客户开始服务
lastServed = customer;
currentTime += customer.serviceTime;
totalWaitTime += currentTime - customer.arrivalTime;
servedCount++;
}
// 最后一个客户离开后到结束时间也算为空闲时间(如果设定有结束时间,需相应调整)
printf("Total idle time: %d\n", idleTime);
if (servedCount > 0) {
printf("Average waiting time: %.2f\n", (float)totalWaitTime / servedCount);
} else {
printf("No customers served.\n");
}
free(queue->array);
free(queue);
return 0;
}
解析代码:
-
客户信息结构体:定义了
Customer
结构体类型,用于存储与客户相关的信息。结构体内部包含两个整型成员变量arrivalTime(
表示客户到达的时间)和serviceTime(
表示客户办理业务所需要的时间)
-
简单的队列实现:定义了
Queue
结构体,用于实现一个基于数组的简单队列数据结构结构体内部包含:
Customer *array(
指向Customer
结构体类型的指针,用于动态分配存储队列中元素的数组。每个元素都是一个客户信息。)
,int front(
队列的前端(队首)索引。初始化时通常为0,表示队列的第一个元素的位置。在循环队列的实现中,front
指向队列中第一个有效元素。)
int rear(
队列的后端(队尾)索引。它指向下一个将要入队元素的位置,而不是当前队列最后一个元素之后。在循环队列中,rear
在元素入队后会递增,使用取模运算保持在数组范围内。)
int capacity(
队列的最大容量。表示队列能存储的元素数量上限,用于在动态分配数组时确定数组大小。)
int count(
队列当前的实际元素数量。随着元素的入队和出队操作动态变化,用来跟踪队列中已存储的客户信息数量)
3.初始化队列:
initQueue
函数负责创建并初始化一个队列,它接收一个参数 capacity
作为队列的最大容量。
-
声明并分配队列结构体内存:
1Queue *q = (Queue*)malloc(sizeof(Queue));
这行代码通过
malloc
函数动态分配足够的内存来存储一个Queue
结构体,并将其地址赋值给指针q
。类型转换(Queue*)
是为了明确告知编译器分配的内存应被视为Queue
类型的指针。 -
初始化队列属性:
1q->capacity = capacity; 2q->front = q->rear = 0; 3q->count = 0;
分别设置了队列的容量 (
capacity
) 为传入的参数值,队首 (front
) 和队尾 (rear
) 指针都初始化为0,表示队列目前为空,同时计数器 (count
) 初始化为0,表示队列中还没有任何元素。 -
分配客户数组内存:
1q->array = (Customer*)malloc(capacity * sizeof(Customer));
为队列中的客户数组分配内存,能够存储
capacity
个Customer
结构体。每个Customer
结构体都需要一定的内存,通过sizeof(Customer)
获取单个结构体的大小,然后乘以capacity
来得到整个数组所需的内存大小。 -
返回队列指针:
1return q;
最后,函数返回指向新创建队列的指针,这样在调用该函数的地方就可以通过返回的指针操作这个队列了。
4.入队操作:定义了enqueue
函数,用于实现将一个客户数据插入到队列中的操作
-
函数签名:
bool enqueue(Queue *q, Customer data)
-
检查队列是否已满:
if (q->count == q->capacity)
-
函数检查队列当前的元素数量 (
q->count
) 是否等于队列的最大容量 (q->capacity
)。如果队列已满,则函数直接返回false
,表示无法再插入新元素。 -
插入数据:
如果队列未满,则执行以下操作:-
q->array[q->rear] = data;
将新的客户数据data
存储到队列数组 (q->array
) 当前队尾索引 (q->rear
) 处。 -
q->rear = (q->rear + 1) % q->capacity;
更新队尾索引,使其指向下一个位置。这里使用了取模运算 (% q->capacity
) 以实现循环队列的效果,即当队尾索引达到数组末端时,会回到数组的起始位置。 -
q->count++;
增加队列中元素的数量计数器,表示已成功入队一个元素。
-
-
返回结果:
return true;
如果客户数据成功入队,函数返回
true
,表示操作成功。
-
5.出队操作:
- 检查队列是否为空: 如果队列的元素数量
q->count
为0,说明队列为空,函数直接返回false
。 - 出队操作: 否则,将队首元素
q->array[q->front]
复制到*data
中。 - 更新队首指针: 更新队首指针
q->front
到下一个位置,使用取模运算(q->front + 1) % q->capacity
确保指针循环回到数组开头。 - 减小元素计数: 减少队列的元素数量计数
q->count
。 - 返回结果: 函数执行成功后返回
true
。
主函数 (main
函数)
打开文件:
- 使用
fopen
函数尝试以只读模式("r"
)打开文件"data.txt"`。 - 如果文件成功打开,
fopen
返回一个文件指针,否则返回NULL
。 - 如果文件未能成功打开,程序打印错误信息并返回-1,提前终止。
- 初始化队列:
Queue *queue = initQueue(100); // 假设最大客户数量为100
定义变量:
1int currentTime = 0, totalWaitTime = 0, idleTime = 0, servedCount = 0;
2Customer customer, lastServed;
currentTime
:当前时间,初始为0,用于追踪模拟的时间进度。totalWaitTime
:累计的客户等待时间总和,初始为0。idleTime
:空闲时间总和,即服务台没有客户时的累积时间,初始为0。servedCount
:已服务的客户数量,初始为0。customer
:临时变量,用于存储从文件中读取的单个客户信息。lastServed
:记录最近服务完的客户信息,用于计算空闲时间。
读取文件并入队:
1while (fscanf(file, "%d %d", &customer.arrivalTime, &customer.serviceTime) != EOF) {
2 enqueue(queue, customer);
3}
使用fscanf
函数从打开的文件中按行读取数据,每一行预期包含两个整数,分别赋值给customer.arrivalTime
(客户到达时间)和customer.serviceTime
(服务时间)。读取成功后,调用enqueue
函数将customer
对象添加到队列queue
中。这一过程持续到文件结束(EOF
)。
关闭文件:
1fclose(file);
读取完成后,关闭打开的文件以释放系统资源。
6.模拟过程:(银行排队问题核心点)
循环出队客户并处理:
1while (dequeue(queue, &customer)) {
这个循环会一直执行,直到队列为空(无法再出队任何客户)。在每次迭代中,它从队列头部移除一个客户信息至customer
变量中。
更新当前时间:
1currentTime = customer.arrivalTime > currentTime ? customer.arrivalTime : currentTime;
此行确保currentTime
总是至少等于当前处理的客户的到达时间。这样处理是为了准确反映实际情况:如果下一个客户到来时间晚于当前时间,我们的模拟时间才跳到那个时间点。
计算空闲时间:
1if (servedCount > 0) { // 不是第一个客户
2 idleTime += currentTime - lastServed.arrivalTime - lastServed.serviceTime;
3}
如果这不是处理的第一个客户(即至少有一个客户已经被服务过),则计算从上一个客户离开(完成服务)到当前客户到达这段时间的空闲时间,并累加到总空闲时间idleTime
中。
标记客户开始服务:
1lastServed = customer;
记录下当前服务的客户为lastServed
,用于下一次循环计算空闲时间。
更新服务时间和总等待时间:
1currentTime += customer.serviceTime;
2totalWaitTime += currentTime - customer.arrivalTime;
更新currentTime
到当前客户服务完成的时间点,然后累加当前客户等待时间(到达时间到服务完成时间)到总等待时间totalWaitTime
。
增加已服务客户计数:
1servedCount++;
每服务完一个客户,增加已服务客户计数。
7.最后一部分:
空闲时间统计:
1// 最后一个客户离开后到结束时间也算为空闲时间(如果设定有结束时间,需相应调整)
2printf("Total idle time: %d\n", idleTime);
这里假设模拟运行到所有客户都被处理完毕后,直接打印总的空闲时间。
平均等待时间计算与打印:
1if (servedCount > 0) {
2 printf("Average waiting time: %.2f\n", (float)totalWaitTime / servedCount);
3} else {
4 printf("No customers served.\n");
5}
- 如果至少服务了一个客户(
servedCount > 0
),则计算并打印平均等待时间,即所有客户等待时间之和除以客户数量,结果保留两位小数。 - 若没有服务任何客户,打印提示信息"No customers served."。
释放内存:
1free(queue->array);
2free(queue);
释放之前分配的队列结构体及其数组内存,queue->array
是动态分配的客户数组,queue
本身也是通过malloc
分配的。正确释放内存是良好编程习惯,防止内存泄漏。
程序返回:
1return 0;
表示程序正常结束,返回操作系统一个成功执行的状态码0。
要想使用此代码还需加入数据,因此我们需要建立出一个data.txt
-
如何建立data.txt?
- 用Anaconda生成了数据,复制数据至data.txt中即可
我们将用python脚本来创建一个包含客户到达时间和办理业务时间的文本文件。
这个脚本会随机生成一些客户数据,并将其写入文件中。
注意:要先确保电脑环境中已经安装了Python!
-
以下是用于生成示例数据的Python脚本:
import random
import sys
def generate_customer_data(num_customers=20):
"""生成指定数量的客户数据"""
with open('data.txt', 'w') as file:
for _ in range(num_customers):
# 随机生成到达时间(例如,一天中的分钟数,从0到1440)
arrival_time = random.randint(0, 1440)
# 随机生成业务办理时间(例如,1到30分钟)
service_time = random.randint(1, 30)
file.write(f"{arrival_time} {service_time}\n")
if __name__ == "__main__":
num_customers = 20 # 可以修改此值来生成不同数量的客户数据
generate_customer_data(num_customers)
print(f"Generated data for {num_customers} customers in 'data.txt'")
解析脚本:
定义函数generate_customer_data
,它接受一个可选参数num_customers
来指定要生成的客户数量,默认生成20个客户的数据。
每个客户的到达时间是随机选择的一天中的某分钟数(从0到1440分钟),而业务办理时间则是随机的1到30分钟。生成的数据会被写入到data.txt
文件中。
-
将py脚本复制进anaconda并运行
- 在anaconda中运行py脚本后将会自动生成data.txt至C盘(要去C盘用户内寻找生成的data.txt)
- 在D盘所需文件夹里创建文本data.txt,将生成的数据复制进去
- 此时数据已经按照指定格式存在于一个名为"data.txt"的文件中,每行包含两个整数,分别代表客户到达时间和办理业务所需时间。
- (也可以自己尝试修改下脚本,将生成的data.txt直接保存至D盘)
- 以下为博主生成的随机数据
这时 我们先前代码运行所需要的data.txt文件已经彻底准备好了
-
运行dev-c++ !
-
将所需代码复制进dev-c++
-
点击运行与编译
-
运行成功之后将会跳出窗口,即是我们所求业务人员的总空闲时间和客户的平均等待时间
附!如何调试代码?
省流:敬请期待后续文章
博主碎碎念:
data.txt里面数据也可手动修改,将在D盘中创建的data.txt里的数据进行修改,并用ctrl+s保存,再用dev-c++运行,结果会改变的。