Dijkstra算法+递归(上)

数据结构与算法 专栏收录该内容
2 篇文章 0 订阅

Dijkstra算法 用于在非负加权图中查找最短路径 代码如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;

public class Didala {
    private static String start = "";
    private static String end = "";

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        HashMap<String, Integer> cost = new HashMap();
        HashMap<String, String> parents = new HashMap();
        HashMap<String, HashMap<String, Integer>> graph = new HashMap();
        ArrayList<String> flag = new ArrayList();
        initGrpah(cost, parents, graph);
        printGraph(cost, parents, graph);
        didala(flag, cost, parents, graph);
        printGraph(cost, parents, graph);
        getFinalRoute(cost, parents, graph);

    }

    public static void initGrpah(HashMap<String, Integer> cost, HashMap<String, String> parents,
            HashMap<String, HashMap<String, Integer>> graph) {
            //初始化散列表
        System.out.println("输入节点");
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String str = scanner.next();
            if (str.equals("#"))
                break;
            graph.put(str, new HashMap<String, Integer>());
        }
        System.out.println("输入边的权值 起点]终点]长度如 A]B]10 #结束");
        while (scanner.hasNext()) {
            String str = scanner.next();
            if (str.equals("#"))
                break;
            String res[] = str.split("]");
            System.out.println(Arrays.toString(res));
            graph.get(res[0]).put(res[1], Integer.parseInt(res[2]));
        }
        System.out.println("输入开始节点与最终节点");
        String start = scanner.next();
        String end = scanner.next();
        Didala.start = start;
        Didala.end = end;
        HashMap<String, Integer> nextToStart = graph.get(start);
        for (String s : graph.keySet()) {
            if (!s.equals(start)) {
                cost.put(s, Integer.MAX_VALUE);
            }
        }
        for (String s : nextToStart.keySet()) {
            cost.put(s, nextToStart.get(s));
        }
        for (String s : nextToStart.keySet()) {
            parents.put(s, start);
        }

    }

    public static void didala(ArrayList<String> flag, HashMap<String, Integer> cost, HashMap<String, String> parents,
            HashMap<String, HashMap<String, Integer>> graph) {
            //该方法是Dijkstra算法主体
        String s = findLowestCost(cost, flag);
        while (s != null) {
            Integer val = cost.get(s);
            HashMap<String, Integer> neighbor = graph.get(s);
            for (String str : neighbor.keySet()) {
                int newCost = val + neighbor.get(str);
                if (newCost < cost.get(str)) {
                    cost.put(str, newCost);
                    parents.put(str, s);
                    flag.add(s);
                    //★
                }
            }
            s = findLowestCost(cost, flag);
        }
    }

    public static String findLowestCost(HashMap<String, Integer> cost, ArrayList<String> flag) {
        int used = Integer.MAX_VALUE;
        String key = null;
        for (String s : cost.keySet()) {
            if (!flag.contains(s) && cost.get(s) <= used) {
                key = s;
                used = cost.get(s);
            }
        }
        if (key != null)
            flag.add(key);
        return key;
    }

    public static void printGraph(HashMap<String, Integer> cost, HashMap<String, String> parents,
            HashMap<String, HashMap<String, Integer>> graph) {
            //输出三个散列表
        System.out.println("graph散列表");
        for (String s : graph.keySet()) {
            System.out.print("节点:" + s + " ");
            HashMap<String, Integer> tem = graph.get(s);
            System.out.print("可达节点[");
            for (String str : tem.keySet()) {
                System.out.print(str + " ");
            }
            System.out.println("]");
        }
        System.out.println("cost散列表");
        for (String s : cost.keySet()) {
            System.out.println("从起点到节点" + s + "所需最小权值为" + cost.get(s));
        }
        System.out.println("parent散列表");
        for (String s : parents.keySet()) {
            System.out.println("节点" + s + "的父节点为" + parents.get(s));
        }
    }

    public static void getFinalRoute(HashMap<String, Integer> cost, HashMap<String, String> parents,
            HashMap<String, HashMap<String, Integer>> graph) {
            //输出权值最小的路径
        String start = Didala.start;
        String end = Didala.end;
        Stack<String> stack = new Stack();
        stack.push(end);
        String str = end;
        while ((str = parents.get(str)) != null) {
            stack.push(str);
        }
        while (stack.size() > 0) {
            String res = stack.pop();
            if (res != null) {
                if (res.equals(start)) {
                    System.out.print("起点:" + res + "->");
                } else if (res.equals(end)) {
                    System.out.print("终点:" + res + "\t路线所需权值最小,为" + cost.get(end));
                } else {
                    System.out.print(res + "->");
                }
            }
        }
    }

}

该算法的局限性在于不能处理含负权边的图,用上述代码测试带负权边的下图。这里写图片描述
按照图中节点输入,程序输出如下:
parent散列表
节点A的父节点为S
节点B的父节点为S
graph散列表
节点:A 可达节点[E ]
节点:B 可达节点[A E ]
节点:S 可达节点[A B ]
节点:E 可达节点[]
cost散列表
从起点到节点A所需最小权值为-90
从起点到节点B所需最小权值为10
从起点到节点E所需最小权值为5
parent散列表
节点A的父节点为B
节点B的父节点为S
节点E的父节点为A
起点:S->B->A->终点:E 路线所需权值最小,为5


得到的路线明显是错误的。最小权值应为 10+(-100)+0 = -90
现在来分析程序执行过程:
首先给起点的邻居赋值 cost[A] = 5; cost[B] = 10;
然后处理节点A cost[终点] = ★5; parents[终点] = A;
然后处理节点B cost[A] = -90; parents[A] = B;
cost[终点] = 9; //比上次更新的值★5大,所以不更新
分析无法处理负权值的原因:
开始假设起点到邻居A的距离5是最短的(实际上并不是,起点->B->A 才是最短的)
后面的计算都是建立在在这个错误的假设之上的,所以都是错误的。
解决方法:当起点到节点X的权值更新的时候(即发现了更小权值的路径),重新计算X的所有邻居(假如邻居也因此有了更小权值的路径,递归)。
首先写一个递归更新节点的方法

public static void updateNode(String key,HashMap<String, Integer> cost, HashMap<String, String> parents,
            HashMap<String, HashMap<String, Integer>> graph){ //key:cost表中更新了权值的节点
        for(String str:graph.get(key).keySet()){    //对于刚刚更新了权值的节点,遍历其邻居。
            if(cost.get(key)+graph.get(key).get(str)<cost.get(str)){ //如果该邻居通过该节点访问权值更低,则更新cost表与parents表
                cost.put(str, cost.get(key)+graph.get(key).get(str));
                parents.put(str, key);
                updateNode(str, cost, parents, graph);
            }
        }
    }
然后  在didala方法中 //★ 处调用
updateNode(str, cost, parents, graph);

程序输出如下:
graph散列表
节点:A 可达节点[E ]
节点:B 可达节点[A E ]
节点:S 可达节点[A B ]
节点:E 可达节点[]
cost散列表
从起点到节点A所需最小权值为-90
从起点到节点B所需最小权值为10
从起点到节点E所需最小权值为-90
parent散列表
节点A的父节点为B
节点B的父节点为S
节点E的父节点为A
起点:S->B->A->终点:E 路线所需权值最小,为-90


接: http://blog.csdn.net/weixin_39670158/article/details/79339718

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页

打赏作者

weixin_39670158

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值