数据结构课程设计(五)——低风险出行系统
前言
数据结构课程设计第五题是每一个同学都不一样的题目,因此很难参考其他同级同学的代码。
这个题应该与北航的一个低风险出行题目类似(其实要比那个简单一点)
我看过了北航的那份源代码(2019年的一个题),感觉代码并不是那么简洁,且好像有一些错误(根据他的代码来看,是用的Floyd算法,但是这个题不是简单一个最短路可以跑完的,可能是我的水平受限,没有get到他的特殊处理)。在下载他的代码进行运行时,发现并没有通过我的数据特例,所以我还是自己重新写了一份。
而且由于这个题应该是个新的原创题,所以还是打算把我的代码和思路share出来,希望同样拿到这个题的大佬学弟学妹们,如果没有思路的话,可以看看我这个最简单、暴力的思路,抛砖引玉一下。
希望不要照抄源代码,这样违背了这个课设的初衷,希望不要被分数和绩点限制了自己的创作力与原创力。
题目要点
先将题目放在这里
简单提炼一下这个题目要做的事情:
①生成数据
可以进行每次程序运行前随机模拟生成数据,也可以程序运行时手动输入数据,也可以由创作者(自己)给定不变数据。
这个题就是一个图,数据包括
10个及以上的节点,每个节点分配风险值(要均匀的分配)
10条以内的一号边(即飞机)
30条以内的二号边(即火车)
不限数量的三号边(即汽车)
②要给定两种最短路解法
第一种是不限时间的,只要风险最低、最终能到目的地即可,求一条简单最短路
第二种是在第一种上加一个条件,要求在限定时间内的最短路
③创立文件
建立文件来记录求得最短路的边的顺序
④模拟时间流动并与用户交互
程序需要内置时间计数器,来模拟时间流动。
而且在用户每次进行键盘输入时,能够让程序接收到,并且输出当前的所在位置(比如9:00时在北京等到5号飞机,该飞机是从北京飞往上海的)
代码讲解
要求就是以上列举的那么多。
一个一个解决。
①对于数据问题
我是允许用户直接输入的,即每次开始前需要用户提供数据所在的文件位置,自动从文件中读取点、边的数据。这个地方可以参考一下当前中国的那个风险分布情况,根据实际情况填写(毕竟所有的程序都是要服务现实的)
②对于最短路解法
这个题目的关键就在于这里。
首先常见的最短路算法也可能只是我见识浅薄,只学过这几种
有Dijkstra算法(贪心算法),Floyd算法(动态规划算法),以及SPFA算法(我感觉是Dijkstra算法的变种)
首先说一下Dijkstra算法,这个算法可以用这个题吗?
不行!!!
为什么不行?因为我觉得不行 我们这个题的特殊点在于,对于一个节点,可能在这个时候某些边不能用,要做抉择,要么选择等一会用这条边,或者是直接不用这条边。那么Dijkstra的特性是当前局部贪心,这就带来一个问题,如何衡量价值,因为贪心贪的也是价值。
先考虑不限时算法
价值用时间来表示吗?不行!因为要最低风险
价值用风险来表示吗?好像也不太行!因为有特例(也是北航程序卡的数据特例)
比如只有三个节点A、B、C,都是中风险地区,一共有以下三条边
A->B 9:00----13:00
A->B 10:00----11:00
B->C 12:00----14:00
起始点为A,目标点为C。
8:00从A点出发,如果以当前点的风险值进行决策,那么就会选择9:00的边,那么到达B点时需要再额外等23小时,显然不是最优解
最优解为在A点等到10:00,乘坐10:00的火车到达C点。
所以可以看到Dijkstra算法好像没法使用至少我没有想到特殊的处理方法
那么Floyd可以吗?也不行!也可能是我动态规划学的太差,用的不好
为什么?
在这个题动态规划的好处在于,可以优化空间复杂度(对于时间复杂度要遍历所有边,和DFS暴力剪枝其实不会差太多,因为暴力剪枝可以进行优化)
考虑几种动态规划法
第一种,只记录到某点的最低风险(显然是错误的,之前的数据特例可以卡掉)
第二种,记录某点到某点的最低风险(也不行,无法保证全局最优,依旧是先前所举的数据特例)
我只想到这两种动态规划法,因此就没用Floyd,还是太菜了
那么能跑的算法是什么呢?
我这里是用的最笨、最暴力的算法,DFS暴力枚举剪枝
首先先对边进行处理,用了三个辅助处理变量来帮忙
主要解释一下clock和risk,因为是dfs所以,可以一直使用下去,所以用clock来记录当前到起始城市的时间,risk来记录风险值,可以一直传递下去。且每一次计算都是由上一条边进行的,所以只要把初始的clock归为出发时间,risk置0,就可以一直使用,不用担心中途出错的问题
//直达交通路线
struct ROUTE
{
//车次号、起止城市、起止时间、跨天数、交通工具
int no;
int start_city, end_city;
int start_time, end_time;
int day;
int tool;
//下面是辅助记录用的
//辅助记录用的是否使用过,到start城市的时间,此时携带的risk
bool use;
int clock;
float risk;
ROUTE()
{
tool = 3;
clock = 0;
use = false;
no = -1;
risk = 0;
}
};
再就是对风险值数组进行处理
我是开了一个三维数组,第一维记录出发城市,第二维记录到达城市,第三维记录是通过哪一条边得到的这个最低风险(否则还是会被特例卡掉,因为要剪枝来及时终止,但是这样也带来问题,如果最优解是最后DFS遍历到的,就会导致几乎要遍历所有的可能才能得到最优解,这也是DFS暴力剪枝的弊端之一)
处理完数据之后,算法就很简单了,routing是外部调用的,route是被调用的递归函数
void graph::routing(int start, int begin, int end)
{
if (begin == end)
{
route_ans = -1;
// cout << 0;
return;
}
//初始化风险值
for (int i = 1; i <= traffic_num; ++i)
{
risk[begin][end][i] = maxrisk;
}
float risks = 0.0;
//开始递归调用
route(start, begin, begin, end, risks);
for (int i = 1; i <= traffic_num; ++i)
{
if (risk[begin][end][i] < route_ans)
{
route_ans = risk[begin][end][i];
}
}
//有答案
if ((route_ans != maxrisk) && (route_ans != -1))
{
// cout << route_ans << endl;
}
//无答案
else
{
route_ans = -1;
// cout << -1 << endl;
}
}
void graph::route(int start, int now, int begin, int end, float risks)
{
// p指针来遍历now节点此时所有的边
SCHEDULE *p = sche[now].nxt;
while (p != nullptr)
{
// p的路线
ROUTE x = p->route;
//这条路的到达时间改为当前时间
x.clock = start;
int timing;
//调整时间,坐上每一辆车
//如果晚于这个车次的出发时间了,第二天坐
if (x.start_time < x.clock)
{
timing = 24 - x.clock;
timing += x.start_time;
x.clock = x.end_time;
}
//等今天发车时坐车
else
{
timing = x.start_time - x.clock;
x.clock = x.end_time;
}
//风险增量
float inc = timing * city[x.start_city].risk;
//到达目的节点
if (x.end_city == end)
{
risk[begin][end][x.no] = min(risk[begin][end][x.no], inc + risks);
x.use = true;
}
//当前这条路线没用过
else if (x.use == false)
{
//现在用过了
p->route.use = true;
float temp = risks + inc;
//更新经过这条边后,起点到终点的最小风险,仅便于剪枝
risk[x.start_city][x.end_city][x.no] = min(risk[x.start_city][x.end_city][x.no], temp);
//继续向下走
route(x.clock, x.end_city, begin, end, temp);
}
//这条路线用过
else
{
//如果这条线之前用过,且这次走的路不优于之前,则说明这条路不够好,退出
if (inc + risks >= risk[x.start_city][x.end_city][x.no])
{
}
//这条路之前用过,但是这次走的路程比之前所用风险更低,则更新
else
{
risk[x.start_city][x.end_city][x.no] = inc + risks;
float temp = risks + inc;
//继续向下走
route(x.clock, x.end_city, begin, end, temp);
}
}
//找下一条边
p = p->nxt;
}
}
对于限时算法
就很简单了,只需要用一个变量来记录时间,如果现在超时了就直接return掉就好,更简单了,算法复杂度更低了
接下来就是说一下细枝末节的问题了
如何接收用户的输入,来返回当前的时间?
用kbhit函数(当然这个地方我调了2个小时,还是比较麻烦的,但是不难)
如何获取获得最优解的线路?
用最笨的方法,用一个临时变量先存起来最优解,然后把数组初始化,重新跑一遍最短路算法,如果当前路的风险值与最优解相等,直接在return的过程中,将路线放入栈中就成功收集到了
源代码
各种数据结构的头文件
#include <iostream>
#include <Windows.h>
#include <vector>
#include <cstdlib>
#include <time.h>
#include <stack>
#include <queue>
#include <fstream>
#include <conio.h>
using namespace std;
//高中低风险度
#define high 0.9
#define mid 0.5
#define low 0.2
//出行方式
#define flg 1
#define tra 2
#define car 3
//乘客状态
#define wait 1
#define way 2
//时间、距离、风险值的无穷大值
#define maxtime 1e4;
#define maxleng 1e4;
const float maxrisk = 1e4;
//直达交通路线
struct ROUTE
{
//车次号、起止城市、起止时间、跨天数、交通工具
//辅助记录用的是否使用过,到start城市的时间,此时携带的risk
int no;
int start_city, end_city;
int start_time, end_time;
int day;
int tool;
bool use;
int clock;
float risk;
ROUTE()
{
tool = 3;
clock = 0;
use = false;
no = -1;
risk = 0;
}
};
//不同城市的时刻表
struct SCHEDULE
{
ROUTE route;
SCHEDULE *nxt = nullptr;
SCHEDULE()
{
nxt = nullptr;
}
};
struct CITY
{
//城市名
string name;
//风险值
float risk;
//该节点为起始点的路线数
int num;
};
class graph
{
private:
//城市数
int city_num;
//交通工具数
int traffic_num;
//风险值
float ***risk;
//城市
CITY *city;
//不同城市为起点的时刻表
SCHEDULE *sche;
//记录最短路线
stack<ROUTE> s;
//用于程序查询
stack<ROUTE> find;
//记录不限时和限时的答案风险值
float route_ans;
float time_route_ans;
public:
graph(int c_n, int r_n)
{
city_num = c_n;
route_ans = maxrisk;
time_route_ans = maxrisk;
traffic_num = r_n;
}
//初始化
void init();
//重置
void reset();
//为了输出路径而进行的重置
void resetroute(int begin, int end);
//添加节点
void addnode(int _no, string &_name, float risk);
//添加边
void addroute(ROUTE &r);
//不限时最短路
void routing(int start, int begin, int end);
//不限时最短路调用递归函数
void route(int start, int now, int begin, int end, float risks);
//查询不限时最短路
void getroute(int start, int now, int begin, int end, float risks, bool &flag);
//查询限时最短路
void gettimeroute(int day, int start, int now, int begin, int end, float risks, int large, bool &flag);
//限时最短路
void timerouting(int start, int begin, int end, int large);
//限时最短路调用递归函数
void timeroute(int day, int start, int now, int begin, int end, float risks, int large);
//输出不限时最短路路径
void output(int start, int begin, int end);
//输出限时最短路路径
void timeoutput(int start, int begin, int end, int large);
//返回路径栈的
stack<ROUTE> ask()
{
stack<ROUTE> res;
while(!find.empty())
{
res.push(find.top());
find.pop();
}
return res;
}
//得到城市数
int getcitynum()
{
return city_num;
}
//返回城市数组
CITY *getcity()
{
return city;
}
//返回不限时/限时的风险值
float get_route_ans() { return route_ans; }
float get_routime_ans() { return time_route_ans; }
};
初始化函数及重置函数所在文件
#include "RA.h"
void graph::init()
{
while (!s.empty())
{
s.pop();
}
city = new CITY[city_num + 1];
risk = new float **[city_num + 1];
sche = new SCHEDULE[city_num + 1];
for (int i = 1; i <= city_num; ++i)
{
risk[i] = new float *[city_num + 1];
for (int j = 1; j <= city_num; ++j)
{
risk[i][j] = new float[traffic_num + 1];
}
city[i].num = 1;
}
for (int i = 1; i <= city_num; ++i)
{
for (int j = 1; j <= city_num; ++j)
{
for (int m = 1; m <= traffic_num; ++m)
{
if (i != j)
{
risk[i][j][m] = maxrisk;
}
else
{
risk[i][j][m] = 0;
}
}
}
}
}
void graph::addnode(int _no, string &_name, float risk)
{
city[_no].name = _name;
city[_no].risk = risk;
}
void graph::addroute(ROUTE &r)
{
ROUTE temp = r;
int start = r.start_city;
int end = r.end_city;
SCHEDULE *new_one;
new_one = new SCHEDULE();
new_one->route = temp;
new_one->nxt = nullptr;
if (city[start].num == 1)
{
++city[start].num;
sche[start].nxt = new_one;
}
else
{
if (city[start].num == 1)
{
sche[start].nxt->nxt = new_one;
++city[start].num;
return;
}
SCHEDULE *p = sche[start].nxt;
SCHEDULE *before = nullptr;
while (p != nullptr)
{
before = p;
p = p->nxt;
}
++city[start].num;
before->nxt = new_one;
}
}
边角功能函数
#include "RA.h"
void graph::reset()
{
//初始化开空间
for (int i = 1; i <= city_num; ++i)
{
SCHEDULE *p = sche[i].nxt;
while (p != nullptr)
{
SCHEDULE *tmp = p->nxt;
delete p;
p = tmp;
}
}
for (int i = 1; i <= city_num; ++i)
{
for (int j = 1; j <= city_num; ++j)
{
for (int m = 1; m <= traffic_num; ++m)
{
if (i != j)
{
risk[i][j][m] = maxrisk;
}
else
{
risk[i][j][m] = 0;
}
}
}
}
}
void graph::resetroute(int begin, int end)
{
for (int i = 1; i <= city_num; ++i)
{
SCHEDULE *p = sche[i].nxt;
while (p != nullptr)
{
p->route.use = false;
p = p->nxt;
}
}
}
void graph::timeoutput(int start, int begin, int end, int large)
{
int day = 0;
ofstream fout;
fout.open("i.out", ios::out);
if (!fout.is_open())
{
return;
}
//无解
if (time_route_ans == -1)
{
fout << "No Path" << endl;
return;
}
resetroute(begin, end);
bool flag = false;
large += start;
gettimeroute(day, start, begin, begin, end, 0.0, large, flag);
int d = 1;
int now_time = start;
while (!s.empty())
{
//暂存入栈中
ROUTE tmp = s.top();
find.push(tmp);
//是当天可以出行
if (tmp.start_time >= now_time)
{
}
else
{
++d;
}
fout << "第" << d << "天:";
fout << "乘坐";
if (tmp.tool == 1)
{
fout << tmp.no << "号"
<< "飞机" << endl;
fout << tmp.start_time << "点从" << city[tmp.start_city].name << "出发" << endl;
fout << tmp.end_time << "点到达" << city[tmp.end_city].name << endl;
fout << endl;
}
else if (tmp.tool == 2)
{
fout << tmp.no << "号"
<< "火车" << endl;
fout << tmp.start_time << "点从" << city[tmp.start_city].name << "出发" << endl;
fout << tmp.end_time << "点到达" << city[tmp.end_city].name << endl;
fout << endl;
}
else if (tmp.tool == 3)
{
fout << tmp.no << "号"
<< "汽车" << endl;
fout << tmp.start_time << "点从" << city[tmp.start_city].name << "出发" << endl;
fout << tmp.end_time << "点到达" << city[tmp.end_city].name << endl;
fout << endl;
}
s.pop();
now_time = tmp.end_time;
}
fout << "到达目的地" << endl;
fout.close();
}
void graph::output(int start, int begin, int end)
{
ofstream fout;
fout.open("i.out", ios::out);
if (!fout.is_open())
{
return;
}
//如果无解
if (route_ans == -1)
{
fout << "No Path" << endl;
return;
}
resetroute(begin, end);
for (int i = 1; i <= city_num; ++i)
{
for (int j = 1; j <= city_num; ++j)
{
for (int m = 1; m <= traffic_num; ++m)
{
if (i != j)
{
risk[i][j][m] = maxrisk;
}
else
{
risk[i][j][m] = 0;
}
}
}
}
bool flag = false;
getroute(start, begin, begin, end, 0.0, flag);
int d = 1;
int now_time = start;
while (!s.empty())
{
ROUTE tmp = s.top();
find.push(tmp);
//是当天可以出行
if (tmp.start_time >= now_time)
{
}
else
{
++d;
}
fout << "第" << d << "天:";
fout << "乘坐";
if (tmp.tool == 1)
{
fout << tmp.no << "号"
<< "飞机" << endl;
fout << tmp.start_time << "点从" << city[tmp.start_city].name << "出发" << endl;
fout << tmp.end_time << "点到达" << city[tmp.end_city].name << endl;
fout << endl;
}
else if (tmp.tool == 2)
{
fout << tmp.no << "号"
<< "火车" << endl;
fout << tmp.start_time << "点从" << city[tmp.start_city].name << "出发" << endl;
fout << tmp.end_time << "点到达" << city[tmp.end_city].name << endl;
fout << endl;
}
else if (tmp.tool == 3)
{
fout << tmp.no << "号"
<< "汽车" << endl;
fout << tmp.start_time << "点从" << city[tmp.start_city].name << "出发" << endl;
fout << tmp.end_time << "点到达" << city[tmp.end_city].name << endl;
fout << endl;
}
now_time = tmp.end_time;
s.pop();
}
fout << "到达目的地";
fout.close();
}
不限时最低风险函数
#include "RA.h"
void graph::routing(int start, int begin, int end)
{
if (begin == end)
{
route_ans = -1;
// cout << 0;
return;
}
//初始化风险值
for (int i = 1; i <= traffic_num; ++i)
{
risk[begin][end][i] = maxrisk;
}
float risks = 0.0;
//开始递归调用
route(start, begin, begin, end, risks);
for (int i = 1; i <= traffic_num; ++i)
{
if (risk[begin][end][i] < route_ans)
{
route_ans = risk[begin][end][i];
}
}
//有答案
if ((route_ans != maxrisk) && (route_ans != -1))
{
// cout << route_ans << endl;
}
//无答案
else
{
route_ans = -1;
// cout << -1 << endl;
}
}
void graph::route(int start, int now, int begin, int end, float risks)
{
// p指针来遍历now节点此时所有的边
SCHEDULE *p = sche[now].nxt;
while (p != nullptr)
{
// p的路线
ROUTE x = p->route;
//这条路的到达时间改为当前时间
x.clock = start;
int timing;
//调整时间,坐上每一辆车
//如果晚于这个车次的出发时间了,第二天坐
if (x.start_time < x.clock)
{
timing = 24 - x.clock;
timing += x.start_time;
x.clock = x.end_time;
}
//等今天发车时坐车
else
{
timing = x.start_time - x.clock;
x.clock = x.end_time;
}
//风险增量
float inc = timing * city[x.start_city].risk;
//到达目的节点
if (x.end_city == end)
{
risk[begin][end][x.no] = min(risk[begin][end][x.no], inc + risks);
x.use = true;
}
//当前这条路线没用过
else if (x.use == false)
{
//现在用过了
p->route.use = true;
float temp = risks + inc;
//更新经过这条边后,起点到终点的最小风险,仅便于剪枝
risk[x.start_city][x.end_city][x.no] = min(risk[x.start_city][x.end_city][x.no], temp);
//继续向下走
route(x.clock, x.end_city, begin, end, temp);
}
//这条路线用过
else
{
//如果这条线之前用过,且这次走的路不优于之前,则说明这条路不够好,退出
if (inc + risks >= risk[x.start_city][x.end_city][x.no])
{
}
//这条路之前用过,但是这次走的路程比之前所用风险更低,则更新
else
{
risk[x.start_city][x.end_city][x.no] = inc + risks;
float temp = risks + inc;
//继续向下走
route(x.clock, x.end_city, begin, end, temp);
}
}
//找下一条边
p = p->nxt;
}
}
void graph::getroute(int start, int now, int begin, int end, float risks, bool &flag)
{
// // the_road代表当前所用的tour指针
// TOUR *the_road = ro;
// p指针来遍历now节点此时所有的边
SCHEDULE *p = sche[now].nxt;
while (p != nullptr)
{
// p的路线
ROUTE x = p->route;
//这条路的到达时间改为当前时间
x.clock = start;
int timing;
//调整时间,坐上每一辆车
//如果晚于这个车次的出发时间了,第二天坐
if (x.start_time < x.clock)
{
timing = 24 - x.clock;
timing += x.start_time;
x.clock = x.end_time;
}
//等今天发车时坐车
else
{
timing = x.start_time - x.clock;
x.clock = x.end_time;
}
//风险增量
float inc = timing * city[x.start_city].risk;
//到达目的节点
if (x.end_city == end)
{
if (inc + risks == route_ans)
{
s.push(x);
flag = true;
return;
}
x.use = true;
}
//当前这条路线没用过
else if (x.use == false)
{
//现在用过了
p->route.use = true;
float temp = risks + inc;
//更新经过这条边后,起点到终点的最小风险,仅便于剪枝
risk[x.start_city][x.end_city][x.no] = min(risk[x.start_city][x.end_city][x.no], temp);
//继续向下走
getroute(x.clock, x.end_city, begin, end, temp, flag);
}
//这条路线用过
else
{
//如果这条线之前用过,且这次走的路不优于之前,则说明这条路不够好,退出
if (inc + risks >= risk[x.start_city][x.end_city][x.no])
{
}
//这条路之前用过,但是这次走的路程比之前所用风险更低,则更新
else
{
risk[x.start_city][x.end_city][x.no] = inc + risks;
float temp = risks + inc;
//继续向下走
getroute(x.clock, x.end_city, begin, end, temp, flag);
}
}
if (flag)
{
s.push(x);
return;
}
//找下一条边
p = p->nxt;
}
}
限时最低风险函数
#include "RA.h"
void graph::timerouting(int start, int begin, int end, int large)
{
if (begin == end)
{
time_route_ans = -1;
// cout << 0;
return;
}
//让时间调整到方便使用的时间
//比如40小时内,但是是7点开始走,则时间调整为47
large += start;
//初始化风险值
for (int i = 1; i <= traffic_num; ++i)
{
risk[begin][end][i] = maxrisk;
}
float risks = 0.0;
//开始递归调用
timeroute(0, start, begin, begin, end, risks, large);
for (int i = 1; i <= traffic_num; ++i)
{
if (risk[begin][end][i] < time_route_ans)
{
time_route_ans = risk[begin][end][i];
}
}
if (time_route_ans != -1 && time_route_ans != maxrisk)
{
// cout << time_route_ans << endl;
}
else
{
time_route_ans = -1;
// cout << -1 << endl;
}
}
void graph::timeroute(int day, int start, int now, int begin, int end, float risks, int large)
{
//先记录下当前的时间,防止day重复改变
int real_day = day;
//超时
if (day * 24 + start > large)
{
return;
}
// p指针来遍历now节点此时所有的边
SCHEDULE *p = sche[now].nxt;
while (p != nullptr)
{
//让day取得最初的时间
day = real_day;
// p的路线
ROUTE x = p->route;
//这条路的到达时间改为当前时间
x.clock = start;
int timing;
//调整时间,坐上每一辆车
//如果晚于这个车次的出发时间了,第二天坐
if (x.start_time < x.clock)
{
timing = 24 - x.clock;
timing += x.start_time;
x.clock = x.end_time;
// day+1,意为隔一天了
++day;
}
//等今天发车时坐车
else
{
timing = x.start_time - x.clock;
x.clock = x.end_time;
}
//风险增量
float inc = timing * city[x.start_city].risk;
//到达目的节点
if (x.end_city == end)
{
//如果时间未超时
if ((x.clock + day * 24 <= large))
{
risk[begin][end][x.no] = min(risk[begin][end][x.no], inc + risks);
}
x.use = true;
}
//当前这条路线没用过
else if (x.use == false)
{
//现在用过了
p->route.use = true;
float temp = risks + inc;
//更新经过这条边后,起点到终点的最小风险,仅便于剪枝
risk[x.start_city][x.end_city][x.no] = min(risk[x.start_city][x.end_city][x.no], temp);
//继续向下走
timeroute(day, x.clock, x.end_city, begin, end, temp, large);
}
//这条路线用过
else
{
//如果这条线之前用过,且这次走的路不优于之前,则说明这条路不够好,退出
if (inc + risks >= risk[x.start_city][x.end_city][x.no])
{
}
//这条路之前用过,但是这次走的路程比之前所用风险更低,则更新
else
{
risk[x.start_city][x.end_city][x.no] = inc + risks;
float temp = risks + inc;
//继续向下走
timeroute(day, x.clock, x.end_city, begin, end, temp, large);
}
}
//找下一条边
p = p->nxt;
}
}
void graph::gettimeroute(int day, int start, int now, int begin, int end, float risks, int large, bool &flag)
{
//先记录下当前的时间,防止day重复改变
int real_day = day;
//超时
if (day * 24 + start > large)
{
return;
}
// p指针来遍历now节点此时所有的边
SCHEDULE *p = sche[now].nxt;
while (p != nullptr)
{
//让day取得最初的时间
day = real_day;
// p的路线
ROUTE x = p->route;
//这条路的到达时间改为当前时间
x.clock = start;
int timing;
//调整时间,坐上每一辆车
//如果晚于这个车次的出发时间了,第二天坐
if (x.start_time < x.clock)
{
timing = 24 - x.clock;
timing += x.start_time;
x.clock = x.end_time;
// day+1,意为隔一天了
++day;
}
//等今天发车时坐车
else
{
timing = x.start_time - x.clock;
x.clock = x.end_time;
}
//风险增量
float inc = timing * city[x.start_city].risk;
//到达目的节点
if (x.end_city == end)
{
//如果时间未超时,且就是答案
if ((x.clock + day * 24 <= large) && (inc + risks == time_route_ans))
{
flag = true;
s.push(x);
return;
}
x.use = true;
}
//当前这条路线没用过
else if (x.use == false)
{
//现在用过了
p->route.use = true;
float temp = risks + inc;
//更新经过这条边后,起点到终点的最小风险,仅便于剪枝
risk[x.start_city][x.end_city][x.no] = min(risk[x.start_city][x.end_city][x.no], temp);
//继续向下走
gettimeroute(day, x.clock, x.end_city, begin, end, temp, large, flag);
}
//这条路线用过
else
{
//如果这条线之前用过,且这次走的路不优于之前,则说明这条路不够好,退出
if (inc + risks >= risk[x.start_city][x.end_city][x.no])
{
}
//这条路之前用过,但是这次走的路程比之前所用风险更低,则更新
else
{
risk[x.start_city][x.end_city][x.no] = inc + risks;
float temp = risks + inc;
//继续向下走
gettimeroute(day, x.clock, x.end_city, begin, end, temp, large, flag);
}
}
if (flag)
{
s.push(x);
return;
}
//找下一条边
p = p->nxt;
}
}
主函数
#include "RA.h"
void read(string &file_name, graph &g);
void asking(int time, graph &g, int start);
void desk();
int main()
{
desk();
string file;
printf("请输入数据地址所在位置:\n");
printf("例如:C:/Users/5115/Desktop/4744303660525717198/5151/i.in\n");
cin >> file;
ifstream fin;
fin.open(file, ios::in);
if (!fin.is_open())
{
cout << "No file\n";
return 0;
}
int city_num;
int route_num;
fin >> city_num >> route_num;
graph g(city_num, route_num);
g.init();
read(file, g);
printf("请选择是否限时出行\n1->限时\n2->不限时\n");
int xxxx;
cin >> xxxx;
int stm;
if (xxxx == 1)
{
int stc, edc, strtime;
printf("请分别输入出行起始时间,出行起始城市,目的城市,限时小时数\n");
cin >> stm >> stc >> edc >> strtime;
g.timerouting(stm, stc, edc, strtime);
g.timeoutput(stm, stc, edc, strtime);
printf("已输出到对应输出文件\n");
if (g.get_routime_ans() == -1)
{
printf("没有找到符合条件路线\n");
return 0;
}
}
else
{
int stc, edc;
printf("请分别输入出行起始时间,出行起始城市,目的城市\n");
cin >> stm >> stc >> edc;
g.routing(stm, stc, edc);
g.output(stm, stc, edc);
printf("已输出到对应输出文件\n");
if (g.get_route_ans() == -1)
{
printf("没有找到符合条件路线\n");
return 0;
}
}
printf("请输入模拟时间秒数,如输入1代表1s模拟路线运行1h\n");
int time;
cin >> time;
printf("\n可以随时输入1进行查询此时旅客状态\n");
asking(time, g, stm);
return 0;
}
void desk()
{
printf("原作者:sdu_cs_s某\n");
printf("提示:该程序只能按照数据要求进行输入数据,若不按数据要求输入,则程序大概率会出错\n");
printf("该程序不支持路线运行跨时超过24h的车次\n");
printf("程序介绍:\n");
printf("本程序为风险出行系统,分为限时和不限时两种模式,同时可以用实际时间模拟程序时间\n");
printf("例如可以现实生活每1s,模拟路线运行1h,实际时间最小单位为1s,模拟时间最小单位为1h\n");
printf("如果有出行路线,则会在该文件夹下建立一个i.out文件,包含路线\n");
printf("本程序支持自定义输入规模数据,由于数据量较大,只支持文件规模读入,数据文件格式如下\n");
printf("城市数 路线数\n");
printf("以下城市数行需要输入(输入顺序即为城市顺序,例如第一行输入的城市,其序号为1):\n城市名 城市风险值(0.2/0.5/0.9)\n");
printf("以下路线数行需要输入(路线方式输入1代表飞机,2代表火车,3代表汽车):起始城市序号 结束城市序号 该路线起始时间 该路线结束时间 路线方式\n");
}
void read(string &file_name, graph &g)
{
ifstream fin;
fin.open(file_name, ios::in);
if (!fin.is_open())
{
cout << "No File";
return;
}
int city;
int rou;
fin >> city >> rou;
for (int i = 1; i <= city; ++i)
{
string name;
float ri;
fin >> name >> ri;
g.addnode(i, name, ri);
}
for (int i = 1; i <= rou; ++i)
{
ROUTE r;
int sc, ec, st, et, to;
fin >> sc >> ec >> st >> et >> to;
r.start_city = sc;
r.end_city = ec;
r.start_time = st;
r.end_time = et;
r.tool = to;
r.no = i;
g.addroute(r);
}
fin.close();
}
void asking(int time, graph &g, int start)
{
CITY *city = new CITY[g.getcitynum() + 1];
CITY *tmp = g.getcity();
for (int i = 1; i <= g.getcitynum(); ++i)
{
city[i].name = tmp[i].name;
city[i].risk = tmp[i].risk;
}
//获取路线顺序
stack<ROUTE> x = g.ask();
//得到总的运行小时数
queue<ROUTE> q;
stack<ROUTE> bak;
int all_time = 0;
if (!x.empty())
{
all_time = x.top().start_time - start;
}
else
{
cout << "No path" << endl;
return;
}
while (!x.empty())
{
q.push(x.top());
bak.push(x.top());
x.pop();
}
while (!q.empty())
{
int before_end = q.front().end_time;
int before_start = q.front().start_time;
x.push(bak.top());
bak.pop();
q.pop();
all_time += (before_end - before_start);
if (q.empty())
{
break;
}
//加上两段旅程中间的过渡时间
else
{
int after_end = q.front().end_time;
int after_start = q.front().start_time;
if (after_start >= before_end)
{
all_time += (after_start - before_end);
}
else
{
all_time += (24 - before_end + after_start);
}
}
}
int clk = start;
int length = 0;
int before = 0;
clock_t s, m;
s = clock();
while (!x.empty())
{
//如果有键盘输入
if (_kbhit())
{
char trash = _getch();
m = clock();
before = length;
length = (m - s) / (1000 * time);
//tp是两次查询的时间差
int tp = length - before;
// tp是需要遍历的小时数
while (tp)
{
--tp;
++clk;
//跨到第二天了
if (clk >= 24)
{
clk -= 24;
}
//当前这辆车次已经遍历完了
if (clk > x.top().end_time)
{
int et = x.top().end_time;
//下一辆
x.pop();
if (x.empty())
{
break;
}
int nxt = x.top().start_time;
//说明下一辆车跨天了
if (nxt < et)
{
//需要调整时钟到第二天
// inc是时钟需要推进的小时数
int inc = 24 - clk;
//如果剩余时间不足以推进到第二天,则用完
if (tp < inc)
{
clk += tp;
break;
}
//剩余时间足够推进到第二天,则进行推进到当天最后一小时,下次直接跨天
else
{
tp -= inc;
//因为不推到第二天,所以要补一个小时回来
++tp;
clk = 23;
}
}
}
}
if (x.empty())
{
break;
}
//在此时的车次上
if (clk >= x.top().start_time && clk <= x.top().end_time)
{
ROUTE r = x.top();
cout << clk << "点";
cout << "在" << r.no << "号";
cout << "从" << city[r.start_city].name << "前往";
cout << city[r.end_city].name << "的";
if (r.tool == 1)
{
cout << "飞机上" << endl;
}
else if (r.tool == 2)
{
cout << "火车上" << endl;
}
else
{
cout << "汽车上" << endl;
}
}
//还坐不到下一辆车
else
{
if (x.empty())
{
break;
}
ROUTE r = x.top();
int now_place = r.start_city;
int r_st = r.start_time;
int r_et = r.end_time;
cout << clk << "点在";
cout << city[now_place].name;
cout << "等待" << r.no << "号";
if (r.tool == 1)
{
cout << "飞机" << endl;
}
else if (r.tool == 2)
{
cout << "火车" << endl;
}
else
{
cout << "汽车" << endl;
}
}
}
m = clock();
//说明程序运行结束了,依旧没有查询,直接结束
if ((m - s) / (1000 * time) >= all_time)
{
break;
}
}
cout << "已到达终点" << endl;
}
i.in(数据文件)
12 35
HaErBin 0.9
ChangSha 0.9
ChengDu 0.9
HeFei 0.9
BeiJing 0.5
ShangHai 0.5
TianJin 0.5
ChongQing 0.5
XiaMen 0.2
LanZhou 0.2
GuiLin 0.2
ChangChun 0.2
9 4 6 8 1
4 9 10 12 1
1 6 12 15 1
6 1 18 21 1
9 3 12 16 1
8 4 7 9 1
10 12 4 8 1
3 5 10 13 2
5 3 14 17 2
12 5 16 18 2
5 12 20 22 2
7 12 2 12 2
12 7 13 23 2
4 6 11 14 2
6 4 15 18 2
3 6 6 19 2
9 8 7 19 2
8 7 8 20 2
12 5 8 14 2
5 12 16 22 2
10 4 4 13 2
10 4 19 23 2
4 10 14 18 2
4 3 0 11 2
3 4 12 23 2
2 1 10 12 3
2 11 3 6 3
2 11 6 8 3
11 6 3 9 3
11 1 3 7 3
2 8 3 10 3
数据图
写在最后
本实验是s某原创,如果有更好的想法欢迎和我交流,有不懂的地方也可以直接私信我
希望学弟学妹们能想到更好的想法(如可视化界面设计,或者更优的算法)