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

本文回顾2017年中兴算法大赛,作者分享了如何使用迪杰斯特拉算法解决最短路径问题,包括必经节点和限制线段的处理策略。通过分析赛题,实现路径排序和最优路径的求解,并对代码进行了详细解释,探讨了算法的改进方向。
摘要由CSDN通过智能技术生成

总结:本人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,保存下来,程序一旦访问到该点,既可以免去重复寻找最优点的时间,大大缩减工程量。
需要源代码的可以在下面留言。。谢谢支持!

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值