sunxh0529 / Travel-Simulation-System

Our production for the Data Structures class:)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

旅行模拟查询系统”设计文档

叶文霆 熊柏桥 董升华

README is lack of some important details and experiments, see more in 实验文档/

一、任务要求

问题描述:

建立一张城市图表,城市之间有三种交通工具(汽车、火车和飞机)相连,某旅客于某一时刻向系统提出旅行要求,系统根据该旅客的要求为其设计一条旅行线路并输出;系统能查询当前时刻旅客所处的地点和状态(停留城市/所在交通工具)。

输入:

第一行一个整数 x,x=0表示是一个设计路线请求,x=1表示是一个查询请求,x=2表示是一个更改旅行计划。

当 x=0 时,第二行接受两个字符串,输入两个字符user_name,password,建立对应的旅行账户; 第三行输入字符串 s(source), d(destination) 和整数 type,表示要求设计一个从 s 城市到 d 城市,旅行策略为 type 的路线。 第四行输入整数Mid_Num,后面是 Mid_Num 个用空格分开的城市名,表示一定要经过的城市。

当 x=1 时,按行依次输入 user_name 和 password,表示查询对应账号的旅行状态。(允许错误输入三次)

当 x=2 时,按行依次输入 user_name 和 password,成功登陆后再输入一个整数 type,表示将当前的旅行计划改为type。

输出:

当输入中 x=0 时,输出新建立账户的 account ID。(输出五位数字); 当输入中 x=1 时,输出对应用户在当前时间的所在地(若输入管理员账户,则输出所有用户的状态); 当输入中 x=2 时,若更改成功则给出对应提示。

难点:

  1. 可维护性数据集的建立与动态修改
  2. 多线程的处理
  3. 用户的 ID,所在地,密码的保存
  4. 设计路线
  • 必须经过若干点的要求
  • 不同旅行策略的分别实现
  1. 实现图形化

二、建立模型

模型假设

考虑到相比于飞机,汽车与火车这类陆地交通方式受限于地缘因素,其交通网络的连通性没有航班的强。举例来说就是:我们考虑从福州到北京,飞机可以不受空间限制直接抵达北京,而火车很可能需要途径上海市作为中转,然后才能到达北京。出于这方面考虑,陆地交通网的建立会比建立空中交通网更为复杂。为了简化模型,在不失去问题普适性的前提下,我们做出以下假设:

  1. 两地之间有直达(不经过其他城市)的火车,当且仅当两地地缘上相邻。
  2. 对于每一种交通工具,其每天的时刻表是固定的。

其意义可以通过图1来直观的理解,即火车只能开在两地之间的边上。

基于以上两个假设,我们考虑两非相邻城市某车次的票价时,我们分别计算其每一条边上的费用,累加起来就是总费用。如Figure 2所示,假设我们要做 G28从福州到北京,我们用福州->合肥加上合肥->北京的票价之和793元来近似真实票价748元,误差为6.01%,说明在允许一定误差的情况下,假设合理。

在命令行版本中,考虑到是第一版本,我们保守地取城市的的个数为N=11。考虑到地缘因素,我们分散性的选取了北京市,沈阳市,呼和浩特市,乌鲁木齐市,西安市,郑州市,上海市,南京市,成都市,广州市和福州市作为我们的研究对象,并在地缘上相近的地区连一条边

采集数据

  1. 对于陆地交通网的时刻表与价格信息,我们通过使用 Python 在铁友网上面通过重复提交表单,利用正则表达式匹配出我们需要的时刻信息与价格(取二等座的价格)。同时为了使图不要过于冗余,城市对之间的重边不超过3个。
  2. 对于航班时刻表,我们不考虑中转航班(在实际中,国内航班里中转航班所占比例也不大),我们对于图中任意城市对都建立条边,同样使用 Python,在去哪儿网上面查找对应的时刻表和价格信息。

三、数据结构说明

class TravelPlan                                      //存储用户旅行方案的类       
{
public:
    int source, destination, type;            
    std::vector<int> station;
};

class People
{
public:
    void Simulate(const int&);                          //模拟经过多少时间 0 <= time <= 1
    void Make_Route(const std::vector<int>&);           //把计算出来的路线赋值给用户
    TravelPlan Get_Plan();
private:
    std::string name, password;
    int id;
    int location;                                       //正表示停留在某一点上,负表示停留在某条边上 abs()为下标
    int duration;                                       //下次位置转移是什么时候
    TravelPlan plan;
    std::deque<int> route;                              //存储未来的旅行路线,存储元素是边序号
};

class TrafficNet                                        //是不是有一种方法可以让整个程序只有一个实例??
{
public:
    void Plan_Route(People);                            //为用户规划路线     
private:
    class Line                                          //内部类 表示一个交通线路
    {
    public:
        std::string name;                               //线路名
        Line *NextLine;                                 //下一条边
        int tail, LeaveTime, duration, cost;            //tail该边的指向的点
    };
    class City
    {
    public:
        Line *FirstLine;                                //第一条边
        std::string name;                               //名字
    };
    City citys[MaxV];
    int CheapWay[MaxV][MaxV];                           //用来求两地最省钱的邻接矩阵
    int MinCost[MaxV][MaxV];                            //用 Floyd 算法求出的最省钱路线
};

TrafficNet net;     

四、设计算法

1. 最少费用策略

在最少费用策略中,我们不关心时间这一维对于路线选择的影响。为了省钱,我们可以花任意时间去等待下一辆最便宜的车。所以我们可以先用 Floyd 算法求出任意点对的最少的到达费用(可以事先处理为二位数据 MinCost[][],储存为离线文件"MinCost.dat",节省初始化时间),剩下的就是考虑我们要怎样经过必经城市,能够使得我们费用最少。这个时候问题就转换为了标准的 TSP ,即旅行商问题。对于旅行商问题有以下两种做法:

A. 朴素的状态压缩 + 记忆化搜索

第一种算法的思路是考虑这样一个状态表达:f[i,s],表示的是现在在城市 i,已经经过了 s 集合里的城市所花费的最少时间(s是一个station.size()位的二进制数,第 i 位为0表示未到达,1表示已到达),状态转移方程如下:

最后的答案显然是在 f[destination, {0, 1, 2,...,N-1}]中。这样状态空间的大小是 n*2^n,当必经城市的数目足够多(n >= 30),即用户要求设计大型旅游线路的时候,对于一个要求及时响应的模拟系统,这样的效率是不可接受的。

所以我们使用考虑使用效率更高的模拟退火算法

B. 模拟退火算法

模拟退火的基本**:

(1) 初始化:初始温度T(充分大),初始解状态S(是算法迭代的起点),每个T值的迭代次数L

(2) 对k=1,……,L做第(3)至第6步:

(3) 产生新解S′

(4) 计算增量Δt′=C(S′)-C(S),其中C(S)为评价函数

(5) 若Δt′<0则接受S′作为新的当前解,否则以概率exp(-Δt′/T)接受S′作为新的当前解.

(6) 如果满足终止条件则输出当前解作为最优解,结束程序。终止条件通常取为连续若干个新解都没有被接受时终止算法。

(7) T逐渐减少,且T->0,然后转第2步。 Quoted from Baidu Encyclopedia 在模拟旅行系统中,我们作如下定义

  1. 评价函数:即为当前解的总路径长度

  2. 产生新解:随机交换当前解中两个点的顺序

  3. 接收函数:接受函数。接受函数决定选择哪一个解决方案,从而可以*避免掉一些局部最优解。

首先我们检查如果相邻的解决方案是比我们目前的解决方案好,如果是,我们接受它。否则的话,我们需要考虑一下影响因素:

  1. 相邻的解决方案有多不好;

  2. 当前的温度有多高。在高温系统下更有可能接受较糟糕的解决方案。

这里我们使用指数型函数进行拟合,P = exp( (solutionEnergy – neighbourEnergy) / temperature ),即上面的 P(dE) = exp( dE/(kT) )

2. 最少时间策略

最小费用策略不同在于,时间这一维在交通方式选择中起到了影响。我们先考虑一个简化问题:不考虑必经城市,在 time = k 时从城市 i 最早能在什么时间到城市 j (等待时间加转移时间)。这是一个三维的问题,首先由于加了时间,我们不能够使用简单的 Floyd 算法求解。考虑在我们的交通网中,有这么一个重要的性质:

由于飞机的速度远远快于陆地交通,因此两个点之间耗时最短的路径,节点个数一般很少,甚至可能就是连接两地的航班

再考虑到我们的图是一个稠密图,我们采用堆优化的Dijkstra算法。考虑到等车时间的存在,在每次新增加一个点x进入集合 S 时,我们松弛集合 V-S中的点时,要增加候车时间,即 (vihecle->depature + 24 - dist[x]) mod 24。(vihicle 是图中的边)

对于不同初始时刻, n 个顶点,我们需要用 O(n logn)的时间求最短路,预处理出leastTime[i][j][k]的时间应该是O(24*n^2*logn)。

License

All codes in this repository are licensed under the terms you may find in the file named "LICENSE" in this directory.

About

Our production for the Data Structures class:)

License:MIT License


Languages

Language:C++ 94.8%Language:Python 3.5%Language:QMake 1.6%