个人项目
2019-07-25 16:18 巴拉巴拉嘟嘟 阅读(327) 评论(0) 收藏 举报| PSP 2.1 | Personal Software Process Stages | Time |
|---|---|---|
| Planning | 计划 | |
| · Estimate | · 估计这个任务需要多少时间 | 1h |
| Development | 开发 | |
| · Analysis | · 需求分析 (包括学习新技术) | 2h |
| · Design Spec | · 生成设计文档 | 1day |
| · Design Review | · 设计复审 (和同事审核设计文档) | - |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | - |
| · Design | · 具体设计 | 1day |
| · Coding | · 具体编码 | 5h |
| · Code Review | · 代码复审 | - |
| · Test | · 测试(自我测试,修改代码,提交修改) | |
| Reporting | 报告 | - |
| · Test Report | · 测试报告 | - |
| · Size Measurement | · 计算工作量 | - |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 1h |
| 合计 |
分析认为,可以使用三个类来代表问题求解所需要的主要数据结构。这三个类包括:
-
地铁站节点类
SubwayNode的一个对象对应着一个地铁站,存储了站名和所属的地铁线名,代表了地铁站与地铁线之间的从属关系。结构伪代码如下:// 地铁站节点类
struct SubwayNode {
String name; // 地铁站的名
String[*] lines; // 地铁站所处的网络
} -
地铁路线类
SubwayPath是一个由 N 个子路线组成的地铁行程路线,其中 N 代表子路线的个数,N == 1 时等效于一个子路线,N == 0 时为无效路线。结构伪代码如下:// 地铁路线类
struct SubwayPath {
String[N] lines; // 每个子路线的所在的地铁线
String[N][*] stations; // 每个子路线的所途经的地铁线
} -
地铁站网络类
SubwayNetwork使用两个字典来存储所有的地铁站和地铁线路,实际代表整个地铁网络结构,结构伪代码如下:// 地铁站网络类
struct SubwayNetwork {
dict{String : SubwayNode} dictNodes; // { 站名 : 站节点 } 的字典
dict{String : String[*]} dictLines; // { 线名 : 沿线站名 } 的字典
}
与这三个类对应的具体功能方法声明如下:
// 返回站 S 所在的所有线
String[*] = Lines(SubwayNode S) {...}
// 返回线 L 上的所有线
SubwayNode[*] = Stations(String L) {...}
// 生成在线 L 上从 Sa 到 Sb 的路线(子路线),注意:
// + 若 Sa 或者 Sb 不在 L 上,则返回无效路线
SubwayPath = MakePath(String L, SubwayNode Sa, SubwayNode Sb) {...}
// 判断路线 P 是否为无效路线
Boolean = IsInvalid(SubwayPath P) {...}
// 合并路线 P1, P2, ... 等, 注意:
// + 若 P1, P2, ... 中任何一个为无效路线,合并结果为无效路线
// + 若前一路线重点与后一路线起点不一致,合并结果为无效路线
SubwayPath = MergePath(SubwayPath P1, SubwayPath P2, ...) {...}
// 选择 P1, P2, ... 最短的路线,注意:
// + 无效的路径比任何的路径都要长
SubwayPath = SelectPath(SubwayPath P1, SubwayPath P2, ...) {...}
// 返回站 Sa 和 Sb 所有的共处的线,注意:
// + 需要基于 SubwayNetwork 存储的结构进行
// + Sa 和 Sb 共处的线可能有多个
// + Sa 和 Sb 不在同一条线上则返回空 String[*]
String[*] = SameLines(SubwayNode Sa, SubwayNode Sb) {...}
// 返回线 La 到 Lb 之间的所有换乘站,注意:
// + 需要基于 SubwayNetwork 存储的结构进行
// + La 到 Lb 之间的换乘站可能有多个
// + 无法从 La 换乘到 Lb 则返回空 SubwayNode[*]
SubwayNode[*] = TransStationsBetween(String La, String Lb) {...}
// 返回站 Sa 前后紧邻的中转站,注意:
// + 需要基于 SubwayNetwork 存储的结构进行
// + 对于每条线的起始站只有一个
// + 对于正常非转乘站有两个
// + 转乘站可能有多个
SubwayNode[*] = NearbyTransStations(SubwayNode Sa) {...}
// 返回站 Sa 到 Sb 的最短路线
// + 需要基于 SubwayNetwork 存储的结构进行
SubwayPath = BestPath(SubwayPath Sa, SubwayPath Sb) {...}
注意,实际上上述方法中的部分都需要在 SubwayNetwork 所存储网络结构的基础上进行,但为了表述清晰,未在函数参数中添加 SubwayNetwork 对象。可认为相关那些函数是 SubwayNetwork 的类方法。
2. 最短路线的查找算法
2.1. 总体思路
本问题实际为一个无权无向图中的最短路径查找,但是如果使用传统图寻径算法进行查找,则忽略的以下问题特性:
-
地铁路线中只有换乘站会和其他站有复杂的连接, 对于非换乘站和其他站的连接简单。故具体地铁路线图节点虽多,但图结构简单,没必要使用针对复杂结构图模型的寻径算法。
-
地铁路线分线,考虑线之后图结构将更简单。常规寻径算法均只考虑节点间连接关系,而不考虑线的存在,没有充分利用这一结构;
故可以针对地铁路线图的特殊性自行设计算法求解。分析认为,存在若想从起始站 Sa 到终点站 Sb,存在着三种情况:
-
Sa和Sb同在一条线上,可通过该线从Sa直达Sb; -
Sa和Sb在不同线上,但存在换乘站St使得可通过一次换乘从Sa到Sb; -
Sa和Sb在不同线上,且需要多次换乘才能到达;
头两种情况相对简单,可通过查找路线图直接得到,对应的两类路线被称之为 “简单路线”。而第三种情况相对复杂,需要借助递归进行查找,对应的路线被成为 “复杂路线”。
2.2. 简单路线的直接查找
首先考虑查找从 Sa 到 Sb 的最短直达路线,基本思路如下:
-
查找
Sa和Sb所共在的线Ls; -
如果
Ls为空说明没法直达,返回一个无效路径; -
如果
Ls不为空,遍历所有Ls上的直达路线,返回最短的;
其程序伪代码如下:
// 得到从 Sa 到 Sb 的最短直达路线,如果不可行返回 INVALID
SubwayPath = BestOneLinesPath(SubwayNode Sa, SubwayNode Sb)
{
// 找 Sa 和 Sb 所共在的线(可能为多个)
String[*] Ls = SameLines(Sa, Sb)
// 如果 Ls 为空,则说明没法直接从 Sa 到 Sb
if IsEmpty(Ls) return INVALID
// 从所有直达路线中找最短的
SubwayPath P = INVALID
for (L[i] in Ls) {
SubwayPath Pi = MakePath(L[i], Sa, Sb)
P = SelectPath(P, Pi)
}
return P
}
再考虑最短的只换乘一次路线的查找,基本思路如下:
-
找从
Sa所在线到Sb所在线所有的换乘方案:-
使用
Lines(Sa)和Lines(Sb)获取所在的线; -
计从
La经过St到Lb的换乘方案为T = (La, St, Lb); -
遍历所有
Sa和Sb所在的线,得到所有的换乘方案Ts;
-
-
如果
Ts为空说明没法只换乘一次到达,返回一个无效路径; -
如果
Ts不为空,遍历所有Ts中所有换乘方案:-
分别得到从
Sa到St以及从St到Sb的直达路线; -
合并两条直达路线为最终路线;
-
选择最短的换乘路线返回;
-
其程序伪代码如下:
// 得到从 Sa 到 Sb 最短的只换乘一次的路线,如果不可行返回 INVALID
SubwayPath = BestTwoLinesPath(SubwayNode Sa, SubwayNode Sb)
{
// 找从 Sa 所在线到 Sb 所在线所有的换乘方案
// 注意 Lines(Sa) 和 Lines(Sb) 均可能为为多个
TrainPlain = struct { String La, SubwayNode St, String Lb };
TrainPlain[*] Ts = TransStationsBetween(Lines(Sa), Lines(Sb))
// 如果 Ts 为空,则说明没法通过换乘一次到达
if IsEmpty(Ts) return INVALID
// 遍历所有只换乘一次的路线,选最短的返回
SubwayPath P = INVALID
for ({La, St, Lb} in Ts) {
// 从 Sa 到 St 的直达路线
SubwayPath Pat = MakePath(La, Sa, St)
// 从 St 到 Sb 的直达路线
SubwayPath Ptb = MakePath(Lb, St, Sb)
// 合并两条路线为最终路线
SubwayPath Pi = MergePath(Pat, Ptb)
// 选 P 和 Pi 中最短的一个
P = SelectPath(P, Pi)
}
return P
}
将两者结合即得到寻找最短的 “简单路线”,对应伪代码如下:
// 得到从 Sa 到 Sb 最短的 “简单路线”,如果不可行返回 INVALID
SubwayPath = BestPlainPath(SubwayNode Sa, SubwayNode Sb) {
return SelectPath(
BestOneLinePath(Sa, Sb),
BestTwoLinesPath(Sa, Sb)
)
}
2.3. 复杂路线的递归查找
当没法直接找到从 Sa 到 Sb 的简单路线,则需要考虑通过递归查找多次换乘的复杂路线。基本的思路是查找起点站 Sa 和终点站 Sb 相邻的各换乘站,递归查找从换乘站到 Sa 或 Sb 的路线。共有三种递归查找方案:
-
从
Sa开始,查找从其相邻的换乘站St到Sb的路线,递归进行直至找到。对应伪代码如下:// 从起点站开始递归查找 Sa 到 Sb 的最佳路线
SubwayPath = BestRecursivePath1(SubwayNode Sa, SubwayNode Sb)
{
// 查找简单路线,如果找到了就返回,终止迭代
SubwayPath P = BestPlainPath(Sa, Sb)
if (! IsInvalid(P)) return P
// 查找 Sa 相邻的换乘站
SubwayNode[*] Ts = NearbyTransStations(Sa)
// 挨个换乘站找看能够找到最佳路线
for (St in Ts) {
// 从 Sa 到 St 的直达路线
SubwayPath Pat = BestOneLinePath(Sa, St)
// 递归查找从 St 到 Sb 的路线
SubwayPath Ptb = BestRecursivePath1(St, Sb)
// 合并两条路线为最终路线,选 P 和 Pi 中最短的一个
SubwayPath Pi = MergePath(Pat, Ptb)
P = SelectPath(P, Pi)
}
return P
} -
从
Sb开始,查找从Sa到其相邻的换乘站St的路线,递归进行直至找到。对应伪代码如下:// 从终点站开始递归查找 Sa 到 Sb 的最佳路线
SubwayPath = BestRecursivePath2(SubwayNode Sa, SubwayNode Sb)
{
// 查找简单路线,如果找到了就返回,终止迭代
Subway P = BestPlainPath(Sa, Sb)
if (! IsInvalid(P)) return P
// 查找 Sb 相邻的换乘站
SubwayNode[*] Ts = NearbyTransStations(Sb)
// 挨个换乘站找看能够找到最佳路线
for (St in Ts) {
// 递归查找从 Sa 到 St 的路线
SubwayPath Pat = BestRecursivePath2(Sa, St)
// 从 St 到 Sb 的直达路线
SubwayPath Ptb = BestOneLinePath(St, Sb)
// 合并两条路线为最终路线,选 P 和 Pi 中最短的一个
SubwayPath Pi = MergePath(Pat, Ptb)
P = SelectPath(P, Pi)
}
return P
} -
从
Sa和Sb两端开始,查找从StA到StB的路线,递归进行直至找到。对应伪代码如下:// 从起点站和终点站开始,双向递归查找 Sa 到 Sb 的最佳路线
SubwayPath = BestRecursivePath3(SubwayNode Sa, SubwayNode Sb)
{
// 查找简单路线,如果找到了就返回,终止迭代
SubwayPath P = BestPlainPath(Sa, Sb)
if (! IsInvalid(P)) return P
// 查找 Sa 和 Sb 相邻的换乘站
SubwayNode[*] TsA = NearbyTransStations(Sa)
SubwayNode[*] TsB = NearbyTransStations(Sb)
// 挨个换乘站找看能够找到最佳路线
for (StA in TsA) for (StB in TsB) {
// 从 Sa 到 StA 的直达路线
SubwayPath Pat = BestOneLinePath(Sa, StA)
// 递归查找从 StA 到 StB 的路线
SubwayPath Ptt = BestRecursivePath3(StA, StB)
// 从 StB 到 StA 的直达路线
SubwayPath Ptb = BestOneLinePath(St, Sb)
// 合并三条路线为最终路线,选 P 和 Pi 中最短的一个
SubwayPath Pi = MergePath(Pat, Ptt, Ptb)
P = SelectPath(P, Pi)
}
return P
}
2.4. 最终路线的查找
最终路线的查找方法即分别尝试三种递归 “复杂路线” 查找(递归查找中同样涵盖了 “简单路线” 的查找),从所找出的三条路线中找到最短的那个作为最终路径。对应伪代码如下:
// 最终最佳路线的查找
SubwayPath = BestPath(SubwayNode Sa, SubwayNode Sb) {
return SelectPath(
BestRecursivePath1(Sa, Sb)
BestRecursivePath2(Sa, Sb)
BestRecursivePath3(Sa, Sb)
)
}
浙公网安备 33010602011771号