这是学校数据结构实验课所给的一个作业
一、题目
“绿色出行”正逐步成为时尚,有些城市开始在固定的站点提供自行车租借服务。设计数据结构及算法完成自行车租借的日常工作的组织与管理。基本管理对象为自行车,每辆自行车用一个id进行唯一标识。每辆自行车存在三种可能状态:
可以租借(available for rent)
已借(rented)
修理中(in repair)
实验要求:
其中在available队列中自行车应该依据其租借的时间总数进行排序,租借时间最少的自行车排在最前面。在rented队列中的自行车应依据其预期返回时间进行排序,排在最前的应是预期最早返回的自行车。
1用三个链表组织三种状态的自行车,初始时有10辆自行车(a-j)可以租借。
2能够实现租借的日常事务:引入新车,租借,收费,修理等。
3租借收费应根据自行车借去的时间计算得出,路程收费标准如下:每小时收费1元
4根据被借的总时间长短(超过5小时)定期对自行车进行维护(维护时间为2小时)。
5还需实现辅助操作:自行车查询,打印全部信息,计算并打印收入(成本及收益)。
6应有完整的界面。
必做要求:
1用三个链表组织三种状态的自行车,初始时有10辆自行车(a-j)可以租借。
2能够实现租借的日常事务:引入新车,租借,修理等。
3在租借状态下的自行车超过5辆以后,排在第一位的自行车以随机20%的概率进行修理维护(租借队列中自行车80%的可能回到available队列,20%的可能进入repair队列)。
4 在修理状态下的自行车超过3辆以后,排在第一位的自行车回到可以租借队列中。
5实现在控制台输入“引入新车,租借,修理”时,能够要求有适当的输入提示如引入新车的编号,并能够分别显示租借或修理的自行车编号,以及三个队列中所有自行车编号序列。
实现提示
主要集中在链队列的基本操作上。
对于题目所给的要求我没有完全实现,如必做要求中的3就没写,必做要求中的5没有做分别显示租借或修理的自行车编号
二、实验步骤
1、设计链表队列并实现
设计链表队列的数据域含有的数据项,包括编号id、租借总时间sumrenttime、开始租借时间rent_time、归还时间return_time、开始修理时间repair_time、修理次数repair、目前状态now。
设计链表队列的操作函数,包括初始化链表队列InitQueue()、销毁链表队列DestroyQueue()、入队EnQueue()、出队DeQueue()、清空队列ClearQueue()、求队列长度QueueLength()、得到队列中第i个元素GetElem()。
代码如下,该代码存放在Queue.h头文件中:
#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;
typedef struct Bicycle{
char id; //自行车编号
int sumrenttime=0; //租借总时间
time_t rent_time=0; //开始租借时间
time_t return_time=0; //归还时间
time_t repair_time=0; //开始修理时间
int repair=0; //修理次数
int now=0; //目前状态(可租借0、已租借1、修理中2)
}Bicycle;
typedef struct QNode{ //队列的结点
Bicycle data; //数据域
struct QNode *next; //指针域
}QNode,*QueuePtr;
typedef struct{ //队列
QueuePtr front; //队头指针,从这头出队
QueuePtr rear; //队尾指针,从这头入队
}LinkQueue;
void InitQueue(LinkQueue&Q){ //初始化链表队列,带头结点
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front) exit(0);
Q.front->next=NULL;
}
void DestroyQueue(LinkQueue&Q){ //销毁链表队列
while(Q.front){
Q.rear=Q.front->next;
free(Q.front);
Q.front=Q.rear;
}
}
void EnQueue(LinkQueue&Q,Bicycle e){ //将Bicycle类型的元素e入队
QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
if(!p) exit(0);
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
}
void DeQueue(LinkQueue&Q,Bicycle&e){ //将队头元素出队,并用e存储数据
if(Q.front==Q.rear) exit(0);
QueuePtr p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p) Q.rear=Q.front;
free(p);
}
void ClearQueue(LinkQueue&Q){ //清空队列
QNode *p = Q.front;
while(p)
{
Q.front = p->next;
delete p;
p = Q.front;
}
}
int QueueLength(LinkQueue &Q){ //求队列的长度
QNode *p=Q.front;
int i=0;
while(p->next!=NULL){
p=p->next;
i++;
}
return i;
}
void GetElem(LinkQueue Q, int i, Bicycle &e){ //得到队列中第i个元素的数据
if(i<1) return; //若i<1错误
QNode *p = Q.front->next; //
for(int k=0; k<i-1; k++)
{
p = p->next;
}
e = p->data;
}
2、在主函数外声明功能函数,包括函数名及变量类型
需要实现的功能函数包括:引入新车addBicycle()、租借rentBicycle()、还车收费fee()、修理repair()、自行车查询search()、打印全部信息PrintAll()、计算并打印收入income()、对队列进行按总租借时间从小到大排序CompareMin()、整理修理中队列Test()。
代码如下,该代码存放在main.cpp文件中 :
#include "Queue.h"
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <math.h>
char fileName[] = "Bicycle.txt"; //存放所有自行车信息的文件
//功能函数
//对队列进行按总租借时间从小到大排序
void CompareMin(LinkQueue &Q){
}
//整理修理中队列,将需要放回到可租借队列的自行车放回
void Test(LinkQueue &Available,LinkQueue &Repair){
}
//租借自行车,将自行车从可租借队列放入已租借队列
void rentBicycle(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
}
//归还自行车并收费
void fee(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
}
//手动将某在可租借队列的自行车送去修理
void repair(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
}
//查询并打印某一自行车的信息
void search(LinkQueue &Q1,LinkQueue &Q2,LinkQueue &Q3){
}
//添加一辆新自行车到可租借队列
void addBicycle(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
}
//将所有自行车数据打印到屏幕
void PrintAll(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair) {
}
//计算并打印收入
void income(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
}
3、编写主函数
(1)建立三个队列:可租借队列Available、已租借队列Rent、修理中队列Repair,并将它们初始化。
(2)将初始的10辆自行车通过for循环语句存入可租借队列。
(3)打印可选择的功能并提示选择对应功能。
(4)根据选择的功能调用对应功能函数。
(5)选择退出系统后,清空三个队列。
int main()
{
LinkQueue Available; //建立一个可租借队列
LinkQueue Rent; //建立一个已租借队列
LinkQueue Repair; //建立一个修理中队列
InitQueue(Available); //初始化队列
InitQueue(Rent); //初始化队列
InitQueue(Repair); //初始化队列
//将初始的10个自行车放入可租借队列
Bicycle y[10];
for(int x=0;x<10;x++){
y[x].id=char('a'+x);
EnQueue(Available,y[x]);
}
int x = 0;
bool out = 0; //判断是否停止使用系统,退出程序
Bicycle e;
while(!out) //提示选择功能
{
Test(Available,Repair);
printf("引入新车(0)\n");
printf("租借(1)\n");
printf("还车收费(2)\n");
printf("修理(3)\n");
printf("自行车查询(4)\n");
printf("打印全部信息(5)\n");
printf("计算并打印收入(6)\n");
printf("退出系统(7)\n");
printf("请输入要选择的功能:");
scanf("%d",&x);
switch(x)
{
case 0: addBicycle(Available,Rent,Repair); break;
case 1: rentBicycle(Available,Rent,Repair); break;
case 2: fee(Available,Rent,Repair); break;
case 3: repair(Available,Rent,Repair); break;
case 4: search(Available,Rent,Repair); break;
case 5: PrintAll(Available,Rent,Repair); break;
case 6: income(Available,Rent,Repair); break;
case 7: out = 1; break; //退出系统
default: printf("选择错误\n\n\n");
}
}
ClearQueue(Available);
ClearQueue(Rent);
ClearQueue(Repair);
}
4、编写功能函数
编写过程中还另外定义了几个功能函数,包括判断两条自行车编号信息是否重复compare()、打印一条自行车信息visit()、保存信息到文件savetoFile()、打印输出文件printFile()、清空文件clearFile()
(1)引入新车addBicycle():
定义一个队列的数据项,将新车的编号写入,比较编号是否与已有自行车重复,若重复停止使用该功能,返回到选择功能界面。若不重复,再将该数据项存入可租借队列。新车的租借总时间为零,不需要对可租借队列进行排序。调用了新定义的函数compare()。
代码如下:
int compare(Bicycle e1, Bicycle e2){
if(e1.id==e2.id) return 1;
else return 0;
}
void addBicycle(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e;
int x=0,l=0;
QNode *p=Available.front->next; //指针指向可租借队列第一辆自行车
printf("输入自行车的编号信息:");
getchar();
scanf("%c", &e.id);
//比较编号是否重复
l=QueueLength(Available);
for(int i=0;i<l;i++){ //比较编号是否与可租借队列的自行车重复
x=compare(e,p->data);
if(x==1){
printf("编号与已有自行车重复,请重新选择功能\n\n\n");
return ;
}
p=p->next;
}
p=Rent.front->next;
l=QueueLength(Rent);
for(int i=0;i<l;i++){ //比较编号是否与已租借队列的自行车重复
x=compare(e,p->data);
if(x==1){
printf("编号与已有自行车重复,请重新选择功能\n\n\n");
return ;
}
p=p->next;
}
p=Repair.front->next;
l=QueueLength(Repair);
for(int i=0;i<l;i++){ //比较编号是否与修理中队列的自行车重复
x=compare(e,p->data);
if(x==1){
printf("编号与已有自行车重复,请重新选择功能\n\n\n");
return ;
}
p=p->next;
}
EnQueue(Available,e);
printf("成功引入编号为%c的新车\n\n\n",e.id);
}
(2)租借rentBicycle():
将可租借队列的队头元素出队,把该元素存入已租借队列,并记录开始租借时间、改变自行车状态。已租借队列中按预期归还时间排序,假设借出最早的自行车预期最早归还,则不需要进行排序,只需要在入队出队时注意操作顺序。
代码如下:
void rentBicycle(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e,e1;
EnQueue(Rent,Available.front->next->data);
DeQueue(Available,e); //将可租借队列的队头元素挪至已租借队列的队尾
Rent.rear->data.now = 1; //改变自行车状态
Rent.rear->data.rent_time = time(NULL); //记录开始租借时间
printf("您已成功租借了编号为%c的自行车\n\n\n",Rent.rear->data.id); //提示租借成功
//这里应当将可租借队列按预期归还时间排序,假设借出最早的自行车预期最早归还
}
(3)还车收费fee():
提示输入要归还的自行车编号,比较编号是否与已租借队列中的自行车相同,若不同则提示输入错误,返回选择功能界面,若相同则计算租借时间,向上取整得到小时数,按1小时1元收费,打印并输出开始租借时间、归还时间、需支付费用。
判断此归还自行车是否需要修理,若距离上次修理又使用了5小时及以上则送去修理,若不需要修理则返回到可租借队列并改变相应数据项。
对该自行车进行操作时,先声明并初始化一个辅助队列,将已租借队列中排在此自行车前面的自行车存入辅助队列,再对该自行车进行操作,操作完后再将已租借队列中剩下的自行车存入辅助队列,再把该辅助队列复制到已租借队列。此操作目的在于保证已租借队列按开始租借时间排序,最早租借的排在队头。
注:
这部分代码有误:关键在于“若距离上次修理又使用了5小时及以上则送去修理”这一句,采用的是租借总时间减去5*修理次数,这是不对的。若用户租赁一辆租借总时间为0的自行车7个小时后还回来,此时租借总时间变为7,需要送去修理。修理完后,下一位用户租借该自行车3小时后还回来,距离上次修理又使用了3个小时,其实不需要送去修理,但按照我的写法其实又送去修理了,与需要完成的功能不符。
解决办法:
在Bicycle中添加一个int类型数据项after_repair,意为距上次修理后自行车又使用的小时数,每次还车时将本次租借的小时数加到该项上,每次修理完后把该项改为零,使用if(after_repair>=5)语句进行判断是否要送去修理。
代码如下:
void fee(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e1,e2;
char id1;
int x=0,n=0;
int j=QueueLength(Rent);
printf("请输入要归还的自行车编号:");
getchar();
scanf("%c",&id1);
for(int k=0; k<j; k++) //查询已租借队列中是否有要归还的自行车
{
GetElem(Rent,k+1,e1); //得到Rent队列中第k+1个元素的数据
if(e1.id==id1){
x=1;
e1.return_time = time(NULL); //得到当前时间
double rent_time_seconds = difftime(e1.return_time,e1.rent_time); //求被租借的时间有多少秒
double rent_time_minutes = rent_time_seconds/60; //求被租借的时间有多少分钟
int rent_time_hours = ceil(rent_time_minutes/60); //向上取整求被租借的时间有多少小时
printf("开始租借时间为:%s",ctime(&e1.rent_time));
printf("归还时间为:%s",ctime(&e1.return_time));
printf("需支付%d元\n\n\n",rent_time_hours); //向客户收费,1小时1元,不满一小时按一小时计
LinkQueue T;
InitQueue(T);
for (int i=0;i<k;i++) //将队列中排在要归还的自行车前面的自行车出队存入队列T
{
EnQueue(T,Rent.front->next->data);
DeQueue(Rent,e2);
}
//判断该刚归还的自行车是否要修理
if(Rent.front->next->data.sumrenttime-(Rent.front->next->data.repair*5)>=5)
{
EnQueue(Repair,Rent.front->next->data);
Repair.rear->data.now = 2;
Repair.rear->data.repair_time = e1.return_time; //记录开始修理时间
Repair.rear->data.sumrenttime = Repair.rear->data.sumrenttime+rent_time_hours; //将被租借的时间按小时加入总租借时间中
DeQueue(Rent,e2);
}
else{ //不修理,返回到可租借队列
EnQueue(Available,Rent.front->next->data);
Available.rear->data.now = 0;
Available.rear->data.repair_time = e1.return_time;
Available.rear->data.sumrenttime = Available.rear->data.sumrenttime + rent_time_hours;
DeQueue(Rent,e2);
CompareMin(Available);
}
n=QueueLength(Rent);
//将已租借队列剩下的自行车存入队列T,再将队列T复制到已租借队列
for (int i=0;i<n;i++) {
EnQueue(T,Rent.front->next->data);
DeQueue(Rent,e2);
}
n=QueueLength(T);
for (int i=0;i<n;i++) { //将T的元素逐个复制给已租借队列
EnQueue(Rent,T.front->next->data);
DeQueue(T,e2);
}
DestroyQueue(T);
break;
}
}
if(x==0){
printf("此自行车编号不属于已租借自行车,请重新选择功能\n\n\n");
}
}
(4)修理repair():
将可租借队列中的自行车送去修理。提示输入要修理的自行车编号,比较编号是否与可租借队列中的自行车相同,若不同则提示输入错误,返回选择功能界面,若相同则把该自行车放入修理中队列并改变数据项中的状态、开始租借时间。
对该自行车进行操作时,先声明并初始化一个辅助队列,将可租借队列中排在此自行车前面的自行车存入辅助队列,再对该自行车进行操作,操作完后再将可租借队列中剩下的自行车存入辅助队列,再把该辅助队列复制到已租借队列。此操作目的在于保证已租借队列按开始租借时间排序,最早租借的排在队头。
代码如下:
void repair(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e1,e2;
int x=0;
int j=QueueLength(Available),n;
char id1;
printf("请输入要修理的自行车编号:");
getchar();
scanf("%c",&id1);
for (int k=0; k<j; k++) { //查询可租借队列中是否有要修理的自行车
GetElem(Available,k+1,e1); //得到Available队列中第k+1个元素的数据
if(e1.id==id1){
x=1;
e1.repair_time = time(NULL);
printf("开始修理时间为:%s\n\n\n",ctime(&e1.repair_time));
LinkQueue T;
InitQueue(T);
for(int i=0;i<k;i++){ //将队列中排在要修理的自行车前面的自行车出队存入队列T
EnQueue(T,Available.front->next->data);
DeQueue(Available,e2);
}
EnQueue(Repair,Available.front->next->data);
Repair.rear->data.repair_time = time(NULL); //得到当前时间为开始修理时间
Repair.rear->data.repair++;
Repair.rear->data.now=2;
DeQueue(Available,e2);
n=QueueLength(Available);
for(int i=0;i<n;i++){ //可租借队列剩下的自行车存入队列T,再将队列T复制到可租借队列
EnQueue(T,Available.front->next->data);
DeQueue(Available,e2);
}
n=QueueLength(T);
for(int i=0;i<n;i++) { //将T的元素逐个复制给可租借队列
EnQueue(Available,T.front->next->data);
DeQueue(T,e2);
}
DestroyQueue(T);
break;
}
j=QueueLength(Available);
}
if(x==0) printf("此自行车编号不属于可租借自行车,请重新选择功能\n\n\n");
}
(5)自行车查询search():
提示输入要查询的自行车编号,在三个队列中查询,若查询到则输出信息,若没有查询到则提示输入错误,返回到功能选择界面。输出信息时调用新定义的功能函数visit()。
代码如下:
void visit(Bicycle e){
printf("编号%c,", e.id);
if(e.now==0){
printf("可租借,");
printf("租借总时间为:%d,",e.sumrenttime);
printf("修理次数为:%d\n\n\n",e.repair);
}
else if(e.now==1){
printf("已租借,");
printf("开始租借时间为:%s,",ctime(&e.rent_time));
printf("修理次数为:%d\n\n\n",e.repair);
}
else if(e.now==2){
printf("修理中,");
printf("开始修理时间为:%s,",ctime(&e.repair_time));
printf("修理次数为:%d\n\n\n",e.repair);
}
}
void search(LinkQueue &Q1,LinkQueue &Q2,LinkQueue &Q3){
char id1;
Bicycle e1,e2;
int n=0;
printf("输入要查询的自行车编号:");
getchar();
scanf("%c",&id1);
for(int k=0; k<QueueLength(Q1) && n==0; k++){ //查询可租借队列中是否有要找的自行车
GetElem(Q1,k+1,e1);
if(e1.id==id1){
visit(e1);
n=1;
break;
}
}
for(int k=0; k<QueueLength(Q2) && n==0; k++){ //查询已租借队列中是否有要找的自行车
GetElem(Q2,k+1,e1);
if(e1.id==id1){
visit(e1);
n=1;
break;
}
}
for(int k=0; k<QueueLength(Q3) && n==0; k++){ //查询修理中队列中是否有要找的自行车
GetElem(Q3,k+1,e1);
if(e1.id==id1){
visit(e1);
n=1;
break;
}
}
if(n==0) printf("此自行车编号不属于已有的自行车,请重新选择功能\n\n\n");
}
(6)打印全部信息PrintAll():
先调用clearFile()函数清空文件,再调用三次savetoFile()函数分别将可租借队列、已租借队列、修理中队列的信息存入文件,最后调用printFile()函数将文件中的信息打印到屏幕上。
代码如下:
void savetoFile(LinkQueue &Q){
Bicycle e;
int j=QueueLength(Q);
FILE *f = fopen(fileName, "a"); //打开文件,a是只写并向文件尾部追加数据
for(int k=0; k<j; k++){ //将队列中的信息打印到文件中
GetElem(Q,k+1,e);
fprintf(f,"编号:%c\t", e.id);
fprintf(f,"状态:");
if(e.now==0){
fprintf(f,"可租借\t");
fprintf(f,"租借总时间:%d\t",e.sumrenttime);
fprintf(f,"修理次数:%d\n",e.repair);
}
else if(e.now==1){
fprintf(f,"已租借\t");
fprintf(f,"开始租借时间:%s\t",ctime(&e.rent_time));
fprintf(f,"修理次数:%d\n",e.repair);
}
else if(e.now==2){
fprintf(f,"修理中\t");
fprintf(f,"开始修理时间:%s\t",ctime(&e.repair_time));
fprintf(f,"修理次数:%d\n",e.repair);
}
}
fclose(f);
}
void printFile(){
FILE *f = fopen(fileName, "r"); //打开文件,r是只读
char str[200]; //存放每一行的信息
while(!feof(f)){
char *p = fgets(str,200,f);
if(p!=NULL) printf("%s\n",str);
}
fclose(f);
}
void clearFile(){
printf("\n\n");
FILE *f = fopen(fileName, "w"); //打开文件,w是只写并向覆盖原文件
fclose(f);
}
void PrintAll(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair) {
clearFile();
savetoFile(Available);
savetoFile(Rent);
savetoFile(Repair);
printFile();
printf("\n\n\n");
}
(7)计算并打印收入income():
采用for循环计算三个队列中所有自行车的租借总时间和修理次数的和,按每小时收费1元,每辆自行车成本200元、修理一次花费10元计算利润及总收入并输出。
代码如下:
void income(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
int sum=0,repair1=0,in=0;
int j;
//计算可租借队列中所有自行车的租借总时间和修理次数
QNode *p=Available.front->next;
j=QueueLength(Available);
for(int i=0;i<j;i++){
sum=sum+p->data.sumrenttime;
repair1=repair1+p->data.repair;
p=p->next;
}
//计算已租借队列中所有自行车的租借总时间和修理次数
p=Rent.front->next;
j=QueueLength(Rent);
for(int i=0;i<j;i++){
sum=sum+p->data.sumrenttime;
repair1=repair1+p->data.repair;
p=p->next;
}
//计算修理中队列中所有自行车的租借总时间和修理次数
p=Repair.front->next;
j=QueueLength(Repair);
for(int i=0;i<j;i++){
sum=sum+p->data.sumrenttime;
repair1=repair1+p->data.repair;
p=p->next;
}
//求总收入和总利润并打印输出
int number=QueueLength(Available)+QueueLength(Rent)+QueueLength(Repair);
in=sum-200*number-10*repair1;
printf("总收入为:%d元\n",sum);
printf("每辆自行车的成本为200元,假设修理一次花费10元,总利润为:%d元\n\n\n",in);
}
(8)对队列进行按总租借时间从小到大排序CompareMin():
采用for循环,遍历寻找当前队列中总租借时间最小的元素。找到后,把在该元素之前的元素从队头出队,存入队尾。然后把该元素存入辅助队列T。
采用for循环将上述操作重复需排序队列的长度次后,辅助队列T即为排序好的队列,将T的元素逐个复制给原队列,排序完成。
代码如下:
void CompareMin(LinkQueue &Q) {
if(Q.front==Q.rear) exit(0);
int j;
LinkQueue T; //新建一个用于排序的队列
InitQueue(T); //初始化用于排序的队列
Bicycle e;
int min=0;
while(Q.front->next!=NULL){
j=QueueLength(Q); //j等于需排序队列的长度
QNode *p=Q.front->next; //新建一个指针
min=p->data.sumrenttime;
for(int i=0;i<j-1;i++) //找到最小
{
p=p->next;
if(p->data.sumrenttime<min){
min=p->data.sumrenttime;
}
}
for(int i=0;i<j;i++){
//若不是最小值,将该元素从队头出队,从队尾入队,
if(Q.front->next->data.sumrenttime>min){
EnQueue(Q,Q.front->next->data);
DeQueue(Q,e);
}
//若是最小值,将该元素出队,放到备用队列
else{
EnQueue(T,Q.front->next->data);
DeQueue(Q,e);
}
}
}
InitQueue(Q);
j=QueueLength(T);
for(int i=0;i<j;i++) //将T的元素逐个复制给Q
{
EnQueue(Q,T.front->next->data);
DeQueue(T,e);
}
}
(9)整理修理中队列:
修理状态下的自行车超过3辆,排在第一位的自行车回到可租借队列中。
修理队列中若有修理完成(修理时间超过2小时)的自行车,将该自行车放回可租借队列。
代码如下:
void Test(LinkQueue &Available,LinkQueue &Repair){
//修理状态下的自行车超过3辆,排在第一位的自行车回到可租借队列中
int j=QueueLength(Repair);
Bicycle e,e1;
if(j>3)
{
EnQueue(Available,Repair.front->next->data);
Available.rear->data.now = 0;
DeQueue(Repair,e);
CompareMin(Available);
}
//修理队列中若有修理完成(修理时间超过2小时)的自行车,将该自行车放回可租借队列
time_t now_time = time(NULL); //当前时间
for (int k=0; k<j; k++) //判断修理中队列中是否有修理完成的自行车
{
GetElem(Repair,k+1,e1); //得到Repair队列中第k+1个元素的数据
double repair_time_seconds = difftime(now_time, e1.repair_time); //求被修理的时间有多少秒
int repair_time_hours = ceil(repair_time_seconds/3600); //求被修理的时间有多少小时
if(repair_time_hours>=2){ //修理时间大于等于2小时,自行车回到可租借队列中
EnQueue(Available,Repair.front->next->data);
Available.rear->data.now = 0;
DeQueue(Repair,e);
CompareMin(Available);
}
}
}
5、对代码进行调试
修改语法错误及语义错误,使其成为能正常运行的控制台程序。
以下为我在调试过程中遇到的问题,由于根据记忆写的,可能有错误:
- Q.front. data,Q.front.next报错,无法编译。解决:不明原因,但都改成Q.front ->data,Q.front->next就可以编译了。
- 在对队列进行操作时发现Q.front->data出错,没有显示相应的数据。解决:发现所用队列是带头结点的队列,需要都改成Q.front->next->data。
- 程序先选择一次功能,第二次选择功能完成后自动结束程序。解决:单步跟踪发现运行到CompareMin()或者EnQueue()函数时报错program received signal sigsegv。CSDN上搜索得可能是对空指针进行操作。在Debug窗口中添加队列的front->next->data等发现是在front->next->data没有分配地址,为空指针的时候对其进行了操作,导致程序直接结束。需要修改函数中使用的for循环语句,避免出现此情况。
- 使用GetElem()函数时自动结束程序。解决:GetElem()函数中if(i<1) exit(0);,exit(0)表示结束程序,把它改为return;意为结束当前void类型函数。
- 使用rentBicycle()函数后,Rent队列、Available队列并非预期的队列。解决:修改rentBicycle()函数的变量,从rentBicycle(LinkQueue Available,LinkQueue Rent,LinkQueue Repair)改为rentBicycle(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair),其他函数也类似。
- 归还自行车并收费时,归还后打印的队列出错,归还后的队列也出错。解决:在代码中大量使用类似于for(int i=0;i<QueueLength(Q);i++)的语句,因为每次循环开始前都重新计算Q队列的长度,导致循环运行次数是原来的一半,出错。将代码中所有类似语句修改,在for循环前添加int j=QueueLength(Q),for循环为:for(int i=0;i<j;i++)。
- 调用fee()函数时,发现自行车的数据项如状态、租借总时间没有对应修改。解决:发现是先用了Bicycle e1;GetElem(Rent,k+1,e1)语句,然后对e1的数据项进行操作,但对e1的地址不是队列的元素,没有在队列的相应元素上进行对应操作。将代码中所有该错误改为对队列的元素的数据项进行操作。
- 调用income()函数时,发现总收入计算错误导致总利润计算错误。解决:单步跟踪运行,发现是for循环中没有令指针p指向下一个元素,导致遍历错误,没有把租借总时间正确地求和,for循环中添加p=p->next语句。
三、待改进的地方
- 改进交互界面,从控制台程序改为windows窗体应用程序。
- 每辆车的成本和每次修理花费可自行设置。
- 自行车的id为char型,若按英文字母编号最多只能有26个,可以改成string型或char型数组
- 实现题目要求的:在租借状态下的自行车超过5辆以后,排在第一位的自行车以随机20%的概率进行修理维护(租借队列中自行车80%的可能回到available队列,20%的可能进入repair队列)。
- 将fee()函数中的错误改正。
四、完整代码
1、main.cpp
#include "Queue.h"
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <math.h>
char fileName[] = "Bicycle.txt"; //存放所有自行车信息的文件
//功能函数
//对队列进行按总租借时间从小到大排序
void CompareMin(LinkQueue &Q) {
if(Q.front==Q.rear) exit(0);
int j;
LinkQueue T; //新建一个用于排序的队列
InitQueue(T); //初始化用于排序的队列
Bicycle e;
int min=0;
while(Q.front->next!=NULL){
j=QueueLength(Q); //j等于需排序队列的长度
QNode *p=Q.front->next; //新建一个指针
min=p->data.sumrenttime;
for(int i=0;i<j-1;i++){ //找到最小
p=p->next;
if(p->data.sumrenttime<min){
min=p->data.sumrenttime;
}
}
for(int i=0;i<j;i++){
//若不是最小值,将该元素从队头出队,从队尾入队
if(Q.front->next->data.sumrenttime>min){
EnQueue(Q,Q.front->next->data);
DeQueue(Q,e);
}
//若是最小值,将该元素出队,放到备用队列
else{
EnQueue(T,Q.front->next->data);
DeQueue(Q,e);
}
}
}
InitQueue(Q);
j=QueueLength(T);
for(int i=0;i<j;i++){//将T的元素逐个复制给Q
EnQueue(Q,T.front->next->data);
DeQueue(T,e);
}
}
//整理修理中队列,将需要放回到可租借队列的自行车放回
void Test(LinkQueue &Available,LinkQueue &Repair){
//修理状态下的自行车超过3辆,排在第一位的自行车回到可租借队列中
int j=QueueLength(Repair);
Bicycle e,e1;
if(j>3){
EnQueue(Available,Repair.front->next->data);
Available.rear->data.now = 0;
DeQueue(Repair,e);
CompareMin(Available);
}
//修理队列中若有修理完成(修理时间超过2小时)的自行车,将该自行车放回可租借队列
time_t now_time = time(NULL); //当前时间
for(int k=0; k<j; k++){ //判断修理中队列中是否有修理完成的自行车
GetElem(Repair,k+1,e1); //得到Repair队列中第k+1个元素的数据
double repair_time_seconds = difftime(now_time,e1.repair_time); //求被修理的时间有多少秒
int repair_time_hours = ceil(repair_time_seconds/3600); //求被修理的时间有多少小时
if(repair_time_hours>=2){ //修理时间大于等于2小时,自行车回到可租借队列中
EnQueue(Available,Repair.front->next->data);
Available.rear->data.now = 0;
DeQueue(Repair,e);
CompareMin(Available);
}
}
}
//租借自行车,将自行车从可租借队列放入已租借队列
void rentBicycle(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e,e1;
EnQueue(Rent,Available.front->next->data);
DeQueue(Available,e); //将可租借队列的队头元素挪至已租借队列的队尾
Rent.rear->data.now = 1; //改变自行车状态
Rent.rear->data.rent_time = time(NULL); //记录开始租借时间
printf("您已成功租借了编号为%c的自行车\n\n\n",Rent.rear->data.id); //提示租借成功
//这里应当将可租借队列按预期归还时间排序,假设借出最早的自行车预期最早归还
}
//归还自行车并收费
void fee(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e1,e2;
char id1;
int x=0,n=0;
int j=QueueLength(Rent);
printf("请输入要归还的自行车编号:");
getchar();
scanf("%c",&id1);
for(int k=0; k<j; k++){ //查询已租借队列中是否有要归还的自行车
GetElem(Rent,k+1,e1); //得到Rent队列中第k+1个元素的数据
if(e1.id==id1){
x=1;
e1.return_time = time(NULL); //得到当前时间
double rent_time_seconds = difftime(e1.return_time,e1.rent_time); //求被租借的时间有多少秒
double rent_time_minutes = rent_time_seconds/60; //求被租借的时间有多少分钟
int rent_time_hours = ceil(rent_time_minutes/60); //向上取整求被租借的时间有多少小时
printf("开始租借时间为:%s",ctime(&e1.rent_time));
printf("归还时间为:%s",ctime(&e1.return_time));
printf("需支付%d元\n\n\n",rent_time_hours); //向客户收费,1小时1元,不满一小时按一小时计
LinkQueue T;
InitQueue(T);
//将队列中排在要归还的自行车前面的自行车出队存入队列T
for(int i=0;i<k;i++){
EnQueue(T,Rent.front->next->data);
DeQueue(Rent,e2);
}
//判断该刚归还的自行车是否要修理
if(Rent.front->next->data.sumrenttime-(Rent.front->next->data.repair*5)>=5){
EnQueue(Repair,Rent.front->next->data);
Repair.rear->data.now = 2;
Repair.rear->data.repair_time = e1.return_time; //记录开始修理时间
Repair.rear->data.sumrenttime = Repair.rear->data.sumrenttime+rent_time_hours; //将被租借的时间按小时加入总租借时间中
DeQueue(Rent,e2);
}
//不修理,返回到可租借队列
else{
EnQueue(Available,Rent.front->next->data);
Available.rear->data.now = 0;
Available.rear->data.repair_time = e1.return_time;
Available.rear->data.sumrenttime = Available.rear->data.sumrenttime + rent_time_hours;
DeQueue(Rent,e2);
CompareMin(Available);
}
n=QueueLength(Rent);
//将已租借队列剩下的自行车存入队列T,再将队列T复制到已租借队列
for(int i=0;i<n;i++){
EnQueue(T,Rent.front->next->data);
DeQueue(Rent,e2);
}
n=QueueLength(T);
//将T的元素逐个复制给已租借队列
for(int i=0;i<n;i++){
EnQueue(Rent,T.front->next->data);
DeQueue(T,e2);
}
DestroyQueue(T);
break;
}
}
if(x==0){
printf("此自行车编号不属于已租借自行车,请重新选择功能\n\n\n");
}
}
//手动将某在可租借队列的自行车送去修理
void repair(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e1,e2;
int x=0;
int j=QueueLength(Available),n;
char id1;
printf("请输入要修理的自行车编号:");
getchar();
scanf("%c",&id1);
//查询可租借队列中是否有要修理的自行车
for(int k=0; k<j; k++){
GetElem(Available,k+1,e1); //得到Available队列中第k+1个元素的数据
if(e1.id==id1){
x=1;
e1.repair_time = time(NULL);
printf("开始修理时间为:%s\n\n\n",ctime(&e1.repair_time));
LinkQueue T;
InitQueue(T);
for(int i=0;i<k;i++){ //将队列中排在要修理的自行车前面的自行车出队存入队列T
EnQueue(T,Available.front->next->data);
DeQueue(Available,e2);
}
EnQueue(Repair,Available.front->next->data);
Repair.rear->data.repair_time = time(NULL); //得到当前时间为开始修理时间
Repair.rear->data.repair++;
Repair.rear->data.now=2;
DeQueue(Available,e2);
n=QueueLength(Available);
//可租借队列剩下的自行车存入队列T,再将队列T复制到可租借队列
for(int i=0;i<n;i++){
EnQueue(T,Available.front->next->data);
DeQueue(Available,e2);
}
n=QueueLength(T);
//将T的元素逐个复制给可租借队列
for(int i=0;i<n;i++){
EnQueue(Available,T.front->next->data);
DeQueue(T,e2);
}
DestroyQueue(T);
break;
}
j=QueueLength(Available);
}
if(x==0) printf("此自行车编号不属于可租借自行车,请重新选择功能\n\n\n");
}
//判断两条自行车编号信息是否重复
int compare(Bicycle e1, Bicycle e2){
if(e1.id==e2.id) return 1;
else return 0;
}
//打印自行车e的信息
void visit(Bicycle e){
printf("编号%c,", e.id);
if(e.now==0) {
printf("可租借,");
printf("租借总时间为:%d,",e.sumrenttime);
printf("修理次数为:%d\n\n\n",e.repair);
}
else if(e.now==1){
printf("已租借,");
printf("开始租借时间为:%s,",ctime(&e.rent_time));
printf("修理次数为:%d\n\n\n",e.repair);
}
else if(e.now==2){
printf("修理中,");
printf("开始修理时间为:%s,",ctime(&e.repair_time));
printf("修理次数为:%d\n\n\n",e.repair);
}
}
//查询并打印某一自行车的信息
void search(LinkQueue &Q1,LinkQueue &Q2,LinkQueue &Q3){
char id1;
Bicycle e1,e2;
int n=0;
printf("输入要查询的自行车编号:");
getchar();
scanf("%c",&id1);
for(int k=0; k<QueueLength(Q1) && n==0; k++){ //查询可租借队列中是否有要找的自行车
GetElem(Q1,k+1,e1);
if(e1.id==id1){
visit(e1);
n=1;
break;
}
}
for(int k=0; k<QueueLength(Q2) && n==0; k++){ //查询已租借队列中是否有要找的自行车
GetElem(Q2,k+1,e1);
if(e1.id==id1){
visit(e1);
n=1;
break;
}
}
for(int k=0; k<QueueLength(Q3) && n==0; k++){ //查询修理中队列中是否有要找的自行车
GetElem(Q3,k+1,e1);
if(e1.id==id1){
visit(e1);
n=1;
break;
}
}
if(n==0) printf("此自行车编号不属于已有的自行车,请重新选择功能\n\n\n");
}
//添加一辆自行车到可租借队列
void addBicycle(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
Bicycle e;
int x=0,l=0;
QNode *p=Available.front->next; //指针指向可租借队列第一辆自行车
printf("输入自行车的编号信息:");
getchar();
scanf("%c", &e.id);
//比较编号是否重复
l=QueueLength(Available);
for(int i=0;i<l;i++){ //比较编号是否与可租借队列的自行车重复
x=compare(e,p->data);
if(x==1){
printf("编号与已有自行车重复,请重新选择功能\n\n\n");
return ;
}
p=p->next;
}
p=Rent.front->next;
l=QueueLength(Rent);
for(int i=0;i<l;i++){ //比较编号是否与已租借队列的自行车重复
x=compare(e,p->data);
if(x==1){
printf("编号与已有自行车重复,请重新选择功能\n\n\n");
return ;
}
p=p->next;
}
p=Repair.front->next;
l=QueueLength(Repair);
for(int i=0;i<l;i++){ //比较编号是否与修理中队列的自行车重复
x=compare(e,p->data);
if(x==1){
printf("编号与已有自行车重复,请重新选择功能\n\n\n");
return ;
}
p=p->next;
}
EnQueue(Available,e);
printf("成功引入编号为%c的新车\n\n\n",e.id);
}
//保存信息到文件
void savetoFile(LinkQueue &Q){
Bicycle e;
int j=QueueLength(Q);
FILE *f = fopen(fileName, "a"); //打开文件,a是只写并向文件尾部追加数据
for(int k=0; k<j; k++){ //将队列中的信息打印到文件中
GetElem(Q,k+1,e);
fprintf(f,"编号:%c\t", e.id);
fprintf(f,"状态:");
if(e.now==0){
fprintf(f,"可租借\t");
fprintf(f,"租借总时间:%d\t",e.sumrenttime);
fprintf(f,"修理次数:%d\n",e.repair);
}
else if(e.now==1){
fprintf(f,"已租借\t");
fprintf(f,"开始租借时间:%s\t",ctime(&e.rent_time));
fprintf(f,"修理次数:%d\n",e.repair);
}
else if(e.now==2){
fprintf(f,"修理中\t");
fprintf(f,"开始修理时间:%s\t",ctime(&e.repair_time));
fprintf(f,"修理次数:%d\n",e.repair);
}
}
fclose(f);
}
//打印输出存放自行车数据的文本文件
void printFile(){
FILE *f = fopen(fileName, "r"); //打开文件,r是只读
char str[200]; //存放每一行的信息
while(!feof(f)){
char *p = fgets(str,200,f);
if(p!=NULL) printf("%s\n",str);
}
fclose(f);
}
//将存放自行车数据的文本文件清空
void clearFile(){
printf("\n\n");
FILE *f = fopen(fileName, "w"); //打开文件,w是只写并向覆盖原文件
fclose(f);
}
//将所有自行车数据打印到屏幕
void PrintAll(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair) {
clearFile();
savetoFile(Available);
savetoFile(Rent);
savetoFile(Repair);
printFile();
printf("\n\n\n");
}
//计算并打印收入
void income(LinkQueue &Available,LinkQueue &Rent,LinkQueue &Repair){
int sum=0,repair1=0,in=0;
int j;
//计算可租借队列中所有自行车的租借总时间和修理次数
QNode *p=Available.front->next;
j=QueueLength(Available);
for(int i=0;i<j;i++){
sum=sum+p->data.sumrenttime;
repair1=repair1+p->data.repair;
p=p->next;
}
//计算已租借队列中所有自行车的租借总时间和修理次数
p=Rent.front->next;
j=QueueLength(Rent);
for(int i=0;i<j;i++){
sum=sum+p->data.sumrenttime;
repair1=repair1+p->data.repair;
p=p->next;
}
//计算修理中队列中所有自行车的租借总时间和修理次数
p=Repair.front->next;
j=QueueLength(Repair);
for(int i=0;i<j;i++){
sum=sum+p->data.sumrenttime;
repair1=repair1+p->data.repair;
p=p->next;
}
//求总收入和总利润并打印输出
int number=QueueLength(Available)+QueueLength(Rent)+QueueLength(Repair);
in=sum-200*number-10*repair1;
printf("总收入为:%d元\n",sum);
printf("每辆自行车的成本为200元,假设修理一次花费10元,总利润为:%d元\n\n\n",in);
}
int main()
{
LinkQueue Available; //建立一个可租借队列
LinkQueue Rent; //建立一个已租借队列
LinkQueue Repair; //建立一个修理中队列
InitQueue(Available); //初始化队列
InitQueue(Rent); //初始化队列
InitQueue(Repair); //初始化队列
//将初始的10个自行车放入可租借队列
Bicycle y[10];
for(int x=0;x<10;x++){
y[x].id=char('a'+x);
EnQueue(Available,y[x]);
}
int x = 0;
bool out = 0; //判断是否停止使用系统,退出程序
Bicycle e;
while(!out) //提示选择功能
{
Test(Available,Repair);
printf("引入新车(0)\n");
printf("租借(1)\n");
printf("还车收费(2)\n");
printf("修理(3)\n");
printf("自行车查询(4)\n");
printf("打印全部信息(5)\n");
printf("计算并打印收入(6)\n");
printf("退出系统(7)\n");
printf("请输入要选择的功能:");
scanf("%d",&x);
switch(x)
{
case 0: addBicycle(Available,Rent,Repair); break;
case 1: rentBicycle(Available,Rent,Repair); break;
case 2: fee(Available,Rent,Repair); break;
case 3: repair(Available,Rent,Repair); break;
case 4: search(Available,Rent,Repair); break;
case 5: PrintAll(Available,Rent,Repair); break;
case 6: income(Available,Rent,Repair); break;
case 7: out = 1; break; //退出系统
default: printf("选择错误\n\n\n");
}
}
ClearQueue(Available);
ClearQueue(Rent);
ClearQueue(Repair);
}
2、Queue.h
#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;
typedef struct Bicycle{
char id; //自行车编号
int sumrenttime=0; //租借总时间
time_t rent_time=0; //开始租借时间
time_t return_time=0; //归还时间
time_t repair_time=0; //开始修理时间
int repair=0; //修理次数
int now=0; //目前状态(可租借0、已租借1、修理中2)
}Bicycle;
typedef struct QNode{ //队列的结点
Bicycle data; //数据域
struct QNode *next; //指针域
}QNode,*QueuePtr;
typedef struct{ //队列
QueuePtr front; //队头指针,从这头出队
QueuePtr rear; //队尾指针,从这头入队
}LinkQueue;
void InitQueue(LinkQueue&Q){ //初始化链表队列
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front) exit(0);
Q.front->next=NULL;
}
void DestroyQueue(LinkQueue&Q){ //销毁链表队列
while(Q.front){
Q.rear=Q.front->next;
free(Q.front);
Q.front=Q.rear;
}
}
void EnQueue(LinkQueue&Q,Bicycle e){ //将Bicycle类型的元素e入队
QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
if(!p) exit(0);
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
}
void DeQueue(LinkQueue&Q,Bicycle&e){ //将队头元素出队,并用e存储数据
if(Q.front==Q.rear) exit(0);
QueuePtr p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p) Q.rear=Q.front;
free(p);
}
void ClearQueue(LinkQueue&Q){ //清空队列
QNode *p = Q.front;
while(p)
{
Q.front = p->next;
delete p;
p = Q.front;
}
}
int QueueLength(LinkQueue &Q){ //求队列的长度
QNode *p=Q.front;
int i=0;
while(p->next!=NULL){
p=p->next;
i++;
}
return i;
}
void GetElem(LinkQueue Q, int i, Bicycle &e){ //得到队列中第i个元素的数据
if(i<1) return; //若i<1错误
QNode *p = Q.front->next; //
for(int k=0; k<i-1; k++)
{
p = p->next;
}
e = p->data;
}
结果截图我就不放了,图片太多了。