• 2018.5.7 撸起袖子
  • 2018.5.8 成功迈出第一步
  • 2018.5.9~5.11 我的团队项目博客
  • 2018.5.12 文本数据的格式
  • 2018.5.13 能求最短路径了
  • 2018.5.14 打印一条地铁线上的所有站点
  • 2018.5.15 换乘优化
  • 2018.5.16 鸡肋的全遍历
  • 2018.5.17 /z测试功能
  • 2018.5.18 将C++程序封装成DLL
    • 1. 创建DLL项目
    • 2. 编写需要调用的函数
    • 3. 编译你的DLL
    • 4. 在C#程序里调用DLL
    • 5. 参考
  • 2018.5.19 C#控制台完成
  • 2018.5.20 C#界面主体结构完成
  • 2018.5.21 接近尾声
  • 2015.5.22 GitHub发布Release版本

2018.5.7 撸起袖子

距离前一个项目有一个月了,老师又开始了新的项目,同时软件需求工程与建模课也开了,团队项目也要开始同步了。。。要忙一段时间了,昨前2天刚刚考完计组、数电,然后两周后。。。

结对项目需要两个人,emmmmm,大概就像“双机制”,一个是长机,一个是僚机?又或是狙击手及观察员的关系?总结就是协同配合一起干!不过这搭档也不好找啊,因为如果搭档的实力不够,那么就成了打酱油的了,这样的结对项目就又变成了个人项目,当然还有一种是搭档实力不够,需要有经验的伙伴去带领他打怪升级,这就又有点师徒打野的感觉,师傅负责主力,徒弟捡经验,这模式也还说得过去,只要别总一个劲的划水就行,跟着认真学点经验似乎也行。

我的结对项目博客:软件工程基础课-结对项目-地铁
我的结对项目GitHub: PairProject_Subway
我的结对伙伴的个人博客:Frankin
我的结对伙伴的GitHub:DomiAbraham

这次任务涉及的核心应该有两个方面,一个是问题本身的求解算法,一个是界面编程,同时也要注意分支管理了。

晚上突然无意中翻到自己上个学期数据结构课写的一篇题解,题目是求第一个点与最后一个点之间的最短路径,用的也是Dijkstra算法,这个地铁项目不也是类似吗?只不过是任意两点罢了,然后总结点数量变多了(由于是在可解范围内,不能认为是NP问题)。于是初步尝试修改了一下,基本可用。


2018.5.8 成功迈出第一步

今天尝试了用文件读取地铁站信息,然后任意选两点求最短路径。
地铁站的文本信息格式如下:

& 10 木樨地 383 366
& 11 南礼士路 410 366
& 12 复兴门 441 366
& 13 西单 495 366
& 14 天安门西 537 366
& 15 天安门东 582 366
& 16 王府井 613 366
& 17 东单 639 366
& 18 建国门 688 366
& 19 永安里 722 366# 10 11 2
# 10 11 2
# 10 13 2
# 10 14 2
# 11 12 1
# 12 11 1
# 12 16 2
# 13 12 10
# 13 14 1
# 13 15 1
# 14 10 2
# 14 11 1
# 14 13 1
# 15 17 1
# 17 18 1
# 18 19 1
# 19 14 1

&为地铁站标识符,后面紧接着为地铁站编号站名坐标
#为线路标识符,后面为A站台编号B站台编号A到B的权值,且为单向的。

简单的试验了几个站点,基本能够给出正确解。


2018.5.9~5.11 我的团队项目博客

这段时间没有干活了,因为老师又开团队项目,要组队然后重新开博客。因为开的是博客园的博客,感觉有些简陋,于是套大佬的板子装潢了一下耽搁了些时间。

博客地址:http://www.cnblogs.com/InspAlgo
欢迎来踩!


2018.5.12 文本数据的格式

基本完成/b参数的功能,这部分功能用的算法是Dijkstra算法实现的,由于需要显示换乘路线,故在原文本格式基础上加了路线名。

& 10 木樨地 383 366
& 11 南礼士路 410 366
& 12 复兴门 441 366
& 13 西单 495 366
& 14 天安门西 537 366
& 15 天安门东 582 366
& 16 王府井 613 366
& 17 东单 639 366
& 18 建国门 688 366
& 19 永安里 722 366# 10 11 2 1号线
# 10 11 2 1号线
# 10 13 2 1号线
# 10 14 2 1号线
# 11 12 1 2号线
# 12 11 1 2号线
# 12 16 2 2号线
# 13 12 10 2号线
# 13 14 1 2号线
# 13 15 1 2号线
# 14 10 2 3号线
# 14 11 1 3号线
# 14 13 1 3号线
# 15 17 1 3号线
# 17 18 1 3号线
# 18 19 1 3号线
# 19 14 1 3号线

上面的路线是随便编的。如果相邻两段路线所属的地铁线名不同则输出换乘路线名。

站A--线1-->站B--线2-->站C--线3-->站D

如果线1线2不同,那么在输出站B时同时输出换乘线2。如果相同就不用输出换乘信息了。


2018.5.13 能求最短路径了

今天主要是完成了/b参数功能,顺利使用Dijkstra算法求出最短两点路径,一开始的文本数据格式也是为了配合已有的算法模式而定的。

在测试中发现,基本上能够满足题意要求的最短路径,即最少站,但是在部分用例中会有多条路径是满足题意的,且有的路径同时满足换乘数最少,这种情况应该是符合实际最优解,然而我的程序却输出的并不是这种最优解,只给出了题目要求的解。在后续的编程中,我也尽力在修改,发现可能是文本数据结构的缺陷,一时竟也不好改动,没有什么好的办法。

void Subway::Dijkstra()
{ int book[STATION_NUM];  // book[]节点是否被访问 int dis[STATION_NUM];  // dis[i]起始点到i的最短距离  memset(book, 0, sizeof(book));  // 一开始每个点都没被访问  for (int i = 0; i < STATION_NUM; i++) { dis[i] = this->station_link[this->start_station_][i].value; if (dis[i] < INF)  // start_station_到i有直接路径 { this->station_path[i][0] = this->start_station_; this->station_path[i][1] = i; } } /* 核心语句 */ for (int i = 0; i < STATION_NUM - 1; i++){ int min = INF; int u; for (int j = 0; j < STATION_NUM; j++) { if (book[j] == 0 && dis[j] < min) { min = dis[j]; u = j; } } book[u] = 1; for (int v = 0; v < STATION_NUM; v++) { if (!book[v] && dis[v] > dis[u] + this->station_link[u][v].value) { dis[v] = dis[u] + this->station_link[u][v].value; for (int i = 0; i < STATION_NUM; i++) { this->station_path[v][i] = this->station_path[u][i]; if (this->station_path[v][i] == -1) { this->station_path[v][i] = v; this->station_path_num_ = i + 1; break; } } } } }
}

上面是我在/b参数功能下使用的算法代码,其中station_path[to][i]记录的是到to站点的最短路径中的第i个站点的序号码。

使用数组存路径的一个好处是因为这样在我的路线表示法下显示换乘较为方便。
打印路径的算法见下:

void Subway::PrintPath()
{int flag_output = 0;this->station_path_num_ = 0;for (int i = 0; i < STATION_NUM; i++){if (this->station_path[this->end_station_][i] > -1)this->station_path_num_++;}cout << this->station_path_num_ << endl;for (int i = 0; i < STATION_NUM; i++){if (this->station_path[this->end_station_][i] > -1){cout << this->station_name[this->station_path[end_station_][i]];/* 当前后两条线路名不同说明换乘 */if (i != 0 && this->station_path[this->end_station_][i - 1] > -1&& this->station_path[this->end_station_][i + 1] > -1){if (this->station_link[this->station_path[this->end_station_][i - 1]][this->station_path[this->end_station_][i]].line_name!= this->station_link[this->station_path[this->end_station_][i]][this->station_path[this->end_station_][i + 1]].line_name){cout << " 换乘" << this->station_link[this->station_path[this->end_station_][i]][this->station_path[this->end_station_][i + 1]].line_name;}}cout << endl;flag_output = 1;}}if (!flag_output)cout << "Error: No ways!" << endl;
}

其中的换乘算法的思路是显而易见的,就是按之前的方法比较前后两条线路的地铁线名是否相同,当相同时说明没有换乘,反之换乘。后来自习看了看地铁图,发现这种表示方法有一点点的缺陷,就是在1号线与八通线交接的四惠站和四惠东站这两条地铁线是重合的,而在我的表示方法里这样就有冲突,只能表示一条的情况,当然这点瑕疵虽然不致命,但也不易消除。我又看了看传说中的上海地铁交通线,好吧,我错了,下次再也不这么玩了,好多重复的线,晕,幸亏题目目前要求的是当前的北京地铁。。。老师我错了。。。


2018.5.14 打印一条地铁线上的所有站点

完成了最简单的线路打印,使用/c参数加路线名即可,需注意4号线或大兴线的名字,因为这两条线的命名有点奇葩,所以命名改成4号线/大兴线

具体算法就是纯打表,序号一排,然后依次打印即可,这里就不再赘述了。

目前支持的合法地铁线名有

1号线
2号线
4号线/大兴线
5号线
6号线
7号线
8号线
9号线
10号线
13号线
14号线东线
14号线西线
15号线
16号线
八通线
昌平线
亦庄线
房山线
机场线
西郊线
S1线
燕房线

后来S1线金安桥苹果园之间路线也包含在内,虽然在实际地图中这两站没有连线,但为了方便表示,就默认包含了。


2018.5.15 换乘优化

完成了/d参数功能,换乘优化,以减少换乘次数为目标。
思路如题意,每次换乘实际上是经过3个站点,也就是在处理两个站之间的权值主要由1改为3,即加2,因为要下地铁和再上地铁。

在算法上需要改动原有的Dijkstra算法,即重新修正一下权值。

if (this->transfer_par)  // 在 /d 模式下
{dis[u] = 0;for (int i = 0; i < STATION_NUM; i++){if (i != 0 && this->station_path[u][i - 1] > -1&& this->station_path[u][i + 1] > -1){dis[u] += this->station_link[this->station_path[u][i]][this->station_path[u][i]].value;if (this->station_link[this->station_path[u][i - 1]][this->station_path[u][i]].line_name!= this->station_link[this->station_path[u][i]][this->station_path[u][i + 1]].line_name)dis[u] += this->transfer_par;}}
}

2018.5.16 鸡肋的全遍历

增加/a参数功能,这个功能实现的很鸡肋,主要思路就是选定好起始点from再任取一个未访问点作为to,然后即可求这两点的最短路径,这里直接调用之前写好的Dijkstra()方法即可,然后保存路径后同一输出。再将to赋值给from,再重新选to,直到没有点可选。

这个参数功能的实现让我头疼了好久,因为从查阅的资料来看,大部分人都使用了Fleury算法,这个主要是用来求欧拉回路的,但总感觉这题并不是欧拉回路,因为欧拉回路要遍历所有边,但此题是要求遍历所有车站,前者是对边的遍历,而后者是对点的遍历。既然是对点的遍历,那么可以想到哈密顿回路,这个就是关于点遍历的问题,但是哈密顿回路要求只经历一次点,但在此题条件下是不可能的,因为有些线不是环线,比如你到燕山线、到昌平线、到16号线、到西郊线等等肯定要重复经过一些车站的,不然不可能返回。所以这题应该是非典型哈密顿回路问题,又由于地铁线路的双向性可以考虑使用Fleury算法,不过我看了半天都没弄懂上述算法应该具体怎么实现,机场线还有单向线的特殊情况。于是挣扎了好久后放弃了,就用这种笨办法吧。。。


2018.5.17 /z测试功能

昨天的/a有点问题,换乘似乎不能正确表达,于是改成用数组存储路径,这样之前用的显示换乘的算法就可以用了,同时加上了最后再求一次最后访问的点到起始点的最短路,昨天没有加。

为了遍历方便,将金安桥苹果园之间路线默认加入S1线

完成/z参数功能。考虑到此功能依赖/a的结果,所以将/a的输出改用文本保存,文本名称默认为beijing_subway_traverse.txt,同时每个站点后加一个空格符,因为还要显示换乘,为了在/z下读取站点名称分别,所以使用了空格作为截取标记符。具体的实现算法比较简单,只要用数组做个访问标记就好,这样就知道哪些站点没有访问,而两点之间是否可达只要比较权值即可。

又发现了/d参数功能的一些问题,就是一些用例换乘次数不对,于是改了一下权值更新,主要就是加了dis[v] = dis[u] + this->station_link[u][v].value;同时并把判断条件之一的大于等于改成大于。基本上大部分的换乘结果是正确的,但是有给用例从巴沟十里河是没有从火器营方向走,而是走了另一个方向,虽然都不需要换乘,但是这不是最短路,程序的结果比实际从火器营方向走要多1个站,有点纠结,不过好在大部分情况下是没有问题的。


2018.5.18 将C++程序封装成DLL

至此控制台程序的功能基本上都实现了,现在只剩下界面没有完成了。很纠结是用C++写还是C#写,虽然一开始是纠结是用C++的MFC还是Qt写。后来感觉还是用C#写吧,毕竟用这个写图形界面比较方便,而且也能够非常好地配合VS,同时自己也有一点点C#写界面的经验,相对来说心理接受程度较好。既然决定了用C#写界面,那么之前的命令行功能怎么办呢?用C#重新写一遍?对不起,不存在的。于是查了一下怎么把C++写的程序封装成dll供C#写的程序调用。这里就介绍一下我的方法,可能有些繁琐,若有大佬有更简便的方法还望不吝赐教!在此表以感谢!

1. 创建DLL项目

和创建C++的控制台引用程序一样。我的是VS2017企业版,点击创建新项目,选择Visual C++Windows桌面一栏,于是就可以看到有个动态链接库(DLL)这个选项,点击之后选好项目的位置和名称即可。

2. 编写需要调用的函数

我们在解决方案同名称的DLLCS.cpp文件里看到,最顶上有自动生成的注释

// DLLCS.cpp: 定义 DLL 应用程序的导出函数。
//#include "stdafx.h"

这个文件里就是写需要调用的函数的。

由于是对原先的C++项目封装,那么我们需要把原来的项目文件拷贝到当前项目中,由于我原来的项目是分文件写的,所以很容易把定义类的头文件以及实现具体方法的源文件搬过来,这里放个图片吧,很容易看出区别吧。
这是DLL项目的目录

这是原项目的目录

原项目需要的头文件在DLL项目里重新引用一下就行,没有什么区别。

现在就可以写具体的调用函数了。如我的:

写好后重新建个文件名的头文件,如我的DLLCS.h,里面是你写的调用函数的声明。

注意要加extern "C" __declspec(dllexport),也有将dllexport写成dllimport的,我原本也是dllimport但有些问题,emmm.具体原理就不赘述了,大家可以自行谷歌,因为我也母鸡。。。突然又菜鸡的本质暴露了,瑟瑟发抖~~~

我现在就说一下为什么我的调用函数是无参数的,因为正常情况下我的函数是需要传入int argc, char *argv[]这两个参数的,但是在C#里面传字符串数组参数不太好写,对目前的我是这样的,因为这个问题折腾了一段时间,算了曲线救国吧,于是在C#里面把需要的参数输出到文本,然后在C++里那个InputHandle()方法可以读取文本内容,于是就完成了间接传参。我原本的项目里面InputHandle()方法的是有参数的,因为这个问题特意改了这种写法。然后说一下c#里面写进文本的方法吧。
先声明个using System.IO;

File.WriteAllLines(@"console_input.txt", args, Encoding.Default);

args就是主函数参数string[] argsEncoding.Default是为了解决乱码问题。

然后是关于.def文件,虽然不太清楚具体原理,但看参考的文章似乎只要把调用的函数的函数名在这里写一下就行。这个文件很重要,之前没有写然后调用时报错找不到函数入口。

3. 编译你的DLL

我用的是release x64模式编译的,这里似乎有些坑,有的因为编译模式问题出错,注意一下,具体出错情况我也忘了。。。

4. 在C#程序里调用DLL

先将编译得到的DLL文件移动到../bin/x64/release/目录下,因为我一般使用的是release x64模式,你用什么模式编译运行就放在什么模式的目录下就好。

然后在主函数的那个类中最末位置写下下面这个。

[DllImport("DLLCS.dll", EntryPoint = "Interface")]
public static extern void InterFace();

然后在就可以愉快的调用了。
比如我的

File.WriteAllLines(@"console_input.txt", args, Encoding.Default);
InterFace();

5. 参考

放一些我参考的文章吧,MSDN的以为我本来就会。。。
1. Lib和Dll的那点事
2. 在C#中如何将字符串写入文本文件
3. [科普小短文]在C#中调用C语言函数(静态调用Native DLL,Windows & Microsoft.Net平台)
4. c# 无法加载DLL“###.dll”,: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E),解决办法总结
5. C#对文本文件的读写(转)
6. C#与C++类型互转
7. [新手]C++ 动态库导出函数名“乱码”及解决


2018.5.19 C#控制台完成

C#程序的控制台操作逻辑基本上是完成了,开始界面设计了。弄了个主界面,具体怎么划分还没有想好,目前主要考虑使用北京地铁的图片做背景了,这样就不需要自己画地图了,这样界面也相对简洁美观。


2018.5.20 C#界面主体结构完成

完成了界面主体结构,目前的思路是界面程序读取文本信息,然后根据文本数据的顺序直接画路径即可。画路径的技术主要使用了C#的Graphics类,这个工具可以在窗口上直接绘图,对于我们画路径比较方便。由于画站点和路径的逻辑具有重复性,于是设计了画圈和线这两个主要方法,并在另一个画图方法循环调用即可,同时整个绘图方法封装成了一个DrawTool类以便调用。

这里的绘图方法参考了github.com/jisuozhao/SE2 ,这里表示感谢!


2018.5.21 接近尾声

今天的推送达到了当前历史的最高点,push了6次。。。因为昨天基本完成界面的绘图方法的编写,同时界面也基本上可以响应,所以今天就一口气完成界面的编程。今天的第一次推送是关于依赖库的,因为从C#传入dll的信息的结果是打印在控制台上,而我们界面需要路径信息,方便起见,将dll的运行结果保存在文本文件中,且只需保存站点的坐标即可,然后界面程序读取文本绘制即可。

在运行过程中,发现了一些运行逻辑问题,比如我故意输入一些错误信息看看反馈如何,结果有的模块直接闪退了,界面部分的逻辑主要是读取Error然后再弹出消息窗口显示错误内容,后来一看是dll部分在输出错误信息后直接exit()掉了,由于不是正常结束退出,所以就导致界面直接闪退了。于是修正了dll的退出逻辑,使用bool flag_exit做错误退出标记。

为了方便结对伙伴做站点坐标的采集,又添加了一个新功能,就是显示站点,输入单个站点后界面会将那个站点在地图上圈出来,方便结对伙伴调整坐标信息。

至此,基本完成项目的软件编写,接下来就是一些测试分析和写博客等了。这次光写代码就花了15天,两个星期。。。确实这次难度有点加大了。

绘图部分再说一下,由于我是使用的图片当背景,所以在地图重置时应使用原地图图片进行重置,具体方法见下。

/// <summary>
/// 地图复原,使用原地图覆盖
/// </summary>
public void ResetMap()
{Bitmap bitmap = new Bitmap(Resources.subway_map);Rectangle r = new Rectangle(0, 0,this.pictureBox_Map.Size.Width, this.pictureBox_Map.Size.Height);MainForm.graphics.DrawImage(bitmap, r);  // 使用原地图覆盖
}

程序运行时,特意看了看内存变化,基本上有个峰值然后迅速下降,但至少100MB的内存消耗感觉还是太大,不过还好没有发生内存泄漏问题,这依托于C#的垃圾回收机制,这也是比C++方便的地方,当然最方便的还是写界面,C#写界面总体来说还是相当方便的。


2015.5.22 GitHub发布Release版本

结对伙伴把地图上所有的站点坐标都更新完了,经过试验,发现非常完美。下午把README.md完善了一下,晚上在GitHub正式发布Release版本。结对项目的GitHub部分基本上正式完工。


软件工程基础课-结对项目纪实相关推荐

  1. 软件工程基础课-结对项目-地铁

    一.项目地址 二.PSP 三.解题思路 3.1 面向对象设计与分析 3.2 关键算法的实现 3.2.1 Dijkstra算法 3.2.2 鸡肋的全遍历 四.设计实现过程 4.1 代码风格规范 4.2 ...

  2. 软件工程基础课-个人项目-数独

    一.项目地址 二.PSP 三.解题思路 四.设计实现过程 4.1 代码风格规范 4.2 函数关系图 五.程序性能分析及改进 六.代码说明 七.单元测试与代码覆盖率分析 八.项目总结 8.1 个人的提升 ...

  3. 软件工程导论结对项目

    小学四则运算结对项目 一.项目地址 https://git.coding.net/chenxin1998/Arithmetic.git 结对成员:马乐平,地址:https://git.coding.n ...

  4. 软件工程基础-结对项目-WordCount(单词计数)

    结对项目要求 Github项目地址 https://github.com/richardevan/wordcount PSP表格 PSP2.1 Personal Software Process St ...

  5. [软件工程基础]结对项目——地铁

    目录 一.项目地址与成员博客 二.PSP表格 三.解题思路 3.1.项目需求分析 3.2.线路站点查询 3.3.最短路查询 3.4.全遍历查询 四.设计实现 4.1.程序流程图 4.2.后端实现部分 ...

  6. 软件工程基础结对项目——地铁

    一.GitHub的网址 首先还是先附上GitHub 项目地址:https://github.com/lytning98/subway 队友的博客地址:传送门 二.PSP表格和预估时间 PSP2.1 P ...

  7. [软件工程基础]结对项目 数独程序扩展

    (1)在文章开头给出Github项目地址.(1') 项目地址:https://github.com/JerryYouxin/sudoku (2)在开始实现程序之前,在下述PSP表格记录下你估计将在程序 ...

  8. 现代软件工程 作业 2 结对项目

    这是现代软件工程课的作业 结对项目 Pair Project: 一对同学用结对编程的方法完成 结对编程课件: 现代软件工程讲义 3 结对编程和两人合作 软件工程讲义 3 两人合作(2) 要会做汉堡包 ...

  9. 实验三 软件工程结对项目

    Deadline:2018-4-4 10:00,以博客发表日期为准 评分标准: 按时交 - 有分(满分30分,代码和博客各15分),检查项目包括: -  未提交项目源码到Github上,代码部分不得分 ...

最新文章

  1. Android 实现无网络传输文件(2)
  2. 在.net中如何禁用或启用DropDownList的Items
  3. ecu根据什么信号对点火提前角_关于ECU的那点事
  4. 学长毕业日记 :本科毕业论文写成博士论文的神操作20170404
  5. 牛逼!Python常用数据类型的基本操作(长文系列第一篇)
  6. 退居二线VS在深圳发展,一个十年IT人的选择之难
  7. h5画布动画_如何使用CCapture保存画布动画
  8. php元类,什么是元类-python编程入门系列图文教程-PHP中文网教程
  9. 高中信息技术知识点汇总(必修)
  10. 环路供电和继电保护的含义
  11. 【转】常用0x000000类型颜色代码表
  12. Flutter 实现一款简单的音乐播放器
  13. MPB:西湖大学鞠峰组-​​微生物群落定量宏基因组和宏转录组
  14. Ubuntu安装apex教程
  15. 《那些年啊,那些事——一个程序员的奋斗史》——50
  16. 联想小新Pro14 2023款和2022款区别
  17. 小组项目的初步构建与需求分析
  18. php工程师进行以太坊开发的教程
  19. ESP8266_02程序的编译与下载
  20. Symbian开发环境

热门文章

  1. 逆向学习(二) 安卓逆向
  2. WinRAR 和 RAR注册码
  3. 奇虎360支持Unity引擎 将推3D页游无插件安装
  4. 众享比特未来融合研究院院长:元宇宙数字经济的现状、特征与发展建议
  5. Latex 绘制三线表格
  6. 【树莓派】error: command ‘/usr/bin/arm-linux-gnueabihf-gcc‘ failed with exit code 1(2021年12月)
  7. 教大家分析过掉CF的CRC检测辅助手段
  8. 利用Ginkgo适配器实现一个PM2.5/粉尘物/颗粒物检测仪
  9. 09.JavaScript算术运算符(加、减、乘、除、幂、余)、自增、自减、缩写、位移
  10. AEM技术分享(一)AEM介绍