您的位置:首页 > 编程语言 > Java开发

2017年中兴算法大赛 迪杰特斯拉派

2017-06-23 15:52 471 查看
总结:本人2017年参加的比赛,对于初次参加算法大赛的作者来说,异常激动又有点小窃喜,最后在赛区拿到24名的名次,名次不算高,但是对于一步步解决问题过来的我,经验与经历更为重要,再次做一个小小的总结,也对后续算法提出自己的改进意见,希望对后面参加比赛的你们有一点点启发。

1. 试题介绍



## 赛题 ##最强大脑中的收官蜂巢迷宫变态级挑战,相信大家都叹为观止!最强大脑收官战打响后,收视率节节攀升,就连蚁后也不时出题难为一下她的子民们。在动物世界中,称得上活地图的,除了蜜蜂,蚂蚁当仁不让。在复杂多变的蚁巢中, 蚂蚁总是能以最快、最高效的方式游历在各个储藏间(存储食物)。今天,她看完最新一期节目,又发布了一项新任务:小蚁同学,我需要玉米库的玉米,再要配点水果,去帮我找来吧。小蚁正准备出发,蚁后又说:哎呀,回来,我还没说完呢,还有若干要求如下:

1.小蚁同学,你需要尽可能以最少的花费拿到食物(附件图中路线上的数值表示每两个储物间的花费);

2.小蚁同学,你最多只能经过9个储藏间拿到食物(包含起止两个节点,多次通过同一节点按重复次数计算);

3.小蚁同学,你必须经过玉米间,水果间(附件图中标绿色节点);

4.别忘了,食蚁兽也在路上活动呢,一旦与食蚁兽相遇,性命危矣!不过小蚁微信群公告已经公布了敌人信息(附件图中标红色路段);

5.最后,千万别忘了,还有两段路是必须经过的,那里有我准备的神秘礼物等着你呢(附件图中标绿色路段)。

这下小蚁犯难了,这和它们平时找食物的集体活动规则不一样嘛,看来这次需要单独行动了。要怎么选路呢?小蚁经过一番苦思冥想,稿纸堆了一摞,啊,终于找到了!亲爱的同学们,你们能否也设计一种通用的路径搜索算法,来应对各种搜索限制条件,找到一条最优路径,顺利完成蚁后布置的任务呢?

注:

1、蚁巢,有若干个储藏间(附件图中圆圈表示),储藏间之间有诸多路可以到达(各储藏间拓扑图见附件);

2、节点本身通行无花费;

3、该图为无向图,可以正反两方向通行,两方向都会计费,并且花费相同;

4、起止节点分别为附件图中S点和E点。

5、最优路径:即满足限制条件的路径。

2. 赛题分析

从赛题中,可以简化归纳一下题目的几个要求,首先从S点出发,终点是E,图中寻找最优路径,其中N2-N4,N13-N14为必走线段,N7,N12为不可走的点,N12-N11为不可走的线段,要求在九个点之内,到达终点,起点终点,计算在内,九点走不完,求次优路径。

3. 赛题解析

限制节点算法分析

限制算法关键点提示:

1.必经线段处理:在经过线段一个端点以后,下一步直接经过另一个端点,而不会经由其他节点再绕回另一端点,迪杰斯特拉是通过计算比较最短步长确定下一个节点的,那么,在遇到必经线段的一个端点时,则跳过计算步长比较,直接将另一个端点定为下一个经过节点即可。

2.不可经过线段处理:因为图的存储是利用HashMa来存储节点以及节点之间的步长信息的,因为可以直接在查询操作前,将不可经过节点之间的步长直接删除,简化操作。

3.必经节点处理:令0为起点,分别求出起点到每个必经节点的步长,找到步长最短的必经节点为第一个经过的必经节点(已达节点就可以不用计算了),然后以此为起点,重复上述操作,直到所有必经节点都经过以后,最后到达17终点。

4. 代码分析

对于迪杰斯特拉算法,网上有很多介绍,也有现成的代码,大同小异,本人在拿到赛题以后,也是查阅了很多资料,最终采用了这位博客的代码作为基础来进行后续的延伸,http://blog.csdn.net/xiaojimanman/article/details/50889670,他写的非常的有条理而又清晰,代码完全可以运行,所以我也是非常感谢这位前辈。

拿下迪杰特斯拉基础算法以后,我们可以把赛题的题按照代码的输入的格式进行修改,比较无聊,图很大就麻烦了,很多人采用了矩阵,利用写好的规范图数据,通过代码读取配置文件来加载图,感觉这样更方便,可以采用。

4.1 限制点路径排序

题目要求必经线段可转化为必经点N2与N4, N12与N11,但是只要出现,必成对出现,如果排到N2,则后面强制排序N4,以此类推。

private static void RestrictedRouting(HashMap<Integer, HashMap<Integer, Integer>> stepLength, int[] s,
int[] limit) { // 限制点的路径排序
int Temp;
int min;
int tempStep;
int index; // 依次为临时变量,最短步长,临时步长,索引
distance dis = new DistanceDijkstraImpl();
for (int i = 0, len = s.length - 1; i < len; i++) {
index = i; // index指向当前
min = dis.getMinStep(s[i], s[i + 1], stepLength).getMinStep();
for (int j = i + 1; j < len; j++) {
tempStep = dis.getMinStep(s[i], s[j], stepLength).getMinStep();
System.out.print(tempStep + ","); // 测试:依次输出最短路径
if (min > tempStep) {
min = tempStep;
index = j;
continue; // 找到步长最短的那个索引赋值给later
}
}
System.out.print("index="+index+" ,i="+i);
System.out.println(); //测试:输出当前起始点i,最短路径的索引值
if (index - i > 1) {
Temp = s[index];
s[index] = s[i + 1];
s[i + 1] = Temp; // 如果最短步长索引刚好就是后一位,则不需要换位置,否则调换位置
}
if (s[i] == limit[3] || s[i] == limit[4] || s[i] == limit[5] || s[i] == limit[6]) {
forceSort(s, i, limit); // 每次排序完就找当前索引指向的值是否是线段的某一个端点,如果是,执行方法forceSort,
// 从当前端点往后找到线段对应的另一个端点,放在后一位。比如先找到2.则找到4放在后一位。
}
for (int a : s) {
System.out.print(a + ",");
}
System.out.println(); // 测试:输出需要经过的点临时排序
}
}


private static final void forceSort(int[] s, int i, int[] limit) { // 强制排序
int temp;
if ((s[i] == limit[3] || s[i] == limit[4])) {
for (int k = i + 2; k < s.length; k++) { // 如果定位到线段的某一个端点A,则从A索引值往后找到另一个端点B
// 的索引值,将端点A后一位换成B
if (s[k] == limit[3]) {
i++;
temp = s[k];
s[k] = s[i];
s[i] = temp;
i--;
break;
}
if (s[k] == limit[4]) {
i++;
temp = s[k];
s[k] = s[i];
s[i] = temp;
i--;
break;
}
}
}
if ((s[i] == limit[5] || s[i] == limit[6])) {
for (int k = i + 2; k < s.length; k++) { // 类似,对应端点可假设为C
if (s[k] == limit[6]) {
i++;
temp = s[k];
s[k] = s[i];
s[i] = temp;
i--;
break;
}
if (s[k] == limit[5]) {
i++;
temp = s[k];
s[k] = s[i];
s[i] = temp;
i--;
break;
}
}
}

}




将必须经过的线段先转化为节点,因此题目转化为必须经过7,12,2,4,13,14,用一个s数组存放,其中0为起点,17位终点。

从start=s[i])开始,end=s[j],(i=0,j=i+1)分别算出是s[i]到s[j]的距离L,将距离起点最近的点与s[i+1]交换位置,然后令start=s[i+1],end=s[j],如果遇到s[i+1]=必须经过的线段端点(2-4或13-14),则分析下一个点s[i+2]是否是对应的端点,若果是则不需要变化。否则,从i+3开始寻找对应的端点,找到以后,将其与s[i+2]交换位置,然后令start=s[i+2]继续寻找。

4.2 最优路径排序

先定义一个LinkedList用于存放路径:

然后通过迪杰斯特拉算法,分别求出S数组里结两两节点之间最短路径,如果查找到s[i]=必须经过的线段端点(2-4,13-14),则跳过迪杰斯特拉算法求最短路径,直接将后一位节点(上一步限制路径排序,已经将对应线段节点排序好,所以都是成对出现)存入ls集合,最短步长直接利用HashMap的key来求步长value。

private static void OptimalPathJudgment(HashMap<Integer, HashMap<Integer, Integer>> stepLength,
int[] s, int[] limit) {
LinkedList<Integer> ls = new LinkedList<Integer>(); // 将路径存入ls
distance dis = new DistanceDijkstraImpl();
MinStep step;  //最短路径
int stepSize = 0;  //步长
ls.add(s[0]); // 把起点放入S数组
for (int i = 0, len = s.length - 1; i < len; i++) {
if ((s[i] == limit[3] || s[i] == limit[4] || s[i] == limit[5] || s[i] == limit[6])) {
stepSize += stepLength.get(s[i]).get(s[i + 1]);
ls.add(s[++i]);
}
step = dis.getMinStep(s[i], s[i + 1], stepLength); //得到相邻两节点最短路径
stepSize += step.getMinStep();    //起点到可达节点步长总和
step.getStep().remove(0);  //将路径第一个节点删除
ls.addAll(step.getStep());  //将最短路径全部放入ls集合中
System.out.println("step: " + step.getStep() + " ,countNum: " +
ls.size() + ls + stepSize);//测试 :输出求取的相邻两点之间的路径,步长
}
if (ls.size() <= 9) {
System.out.println("恭喜你,在条件限制下,能够得到最优路径!在经过" + ls.size() +
"点以后达到终点" + ",最优路径为" + ls + ",最短步长为" + stepSize);
}
if (ls.size() > 9) {
System.out.println("对不起,在条件限制下,无法得到最优路径!为了达到目的可以在经过" + ls.size() +
"点以后达到终点,次优路径为" + ls + "\n"+",最短步长为" +
stepSize+"!你需要修改限制条件重新查找!");
}
}




上面图为测试代码分析,分别将限制节点两两之间的路径取出放入ls集合保存,若遇到必经线段端点(2-4或13-14),则跳过,后面的重复上一步骤。

5.输入结果测试分析





后期总结

工作已经差不多完成了,后面评分建议是小主的论文有些地方叙述的不够清晰完成,叙述的太少了,说真话,你让我最多写5页,不要写太多,所以我缩缩减减,写了6页,但是最后看人间评语夸人家说有人给出了20几个测试结果,加上代码分析,让我很是惊讶,这得多少页啊,说好的大赛论文格式要求,555,伤心。

这题赛题,是没有最优解的,后期我仔细思考过,在代码问题上是没有问题,但是在次优解的倾向上,我没有做出重点分析,究竟是以路径最优,还是限制点数9以内最优,在最短路径的选择上,需要做出更多的分析,

(1)采用迪杰特斯拉算法,判断下一个最优点时,计算该点到下一可达点的路径最小点为最优点,但是实际操作中,会遇到可达路径最小的点不止一个,那么如何判断,该点分析很重要。

(2)我得出的结果是按路径最优算的,没有照点数最优算,读者可自行思考。

(3)随着图的变大,求最优路径的时间复杂度也在变大,算法的选择,也可以参考更多的优秀算法,最近在看多线程高并发,有了一点思考,在导入图,即第一次加载时,将每一个点的下一个最优可达点做好标记,类似HshaMap,的key,value,保存下来,程序一旦访问到该点,既可以免去重复寻找最优点的时间,大大缩减工程量。

需要源代码的可以在下面留言。。谢谢支持!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息