Code前端首页关于Code前端联系我们

Dijkstra 算法案例、局限性和 Java 代码实现

terry 2年前 (2023-09-27) 阅读数 66 #数据结构与算法

Dijkstra 算法(Dijkstra)用于在没有非负权重的情况下计算从起点到每个节点的最短距离

可用 2 类问题: 有从 A 地到 B 地的路吗?

  • 从 A 到 B 的最短路径(最少时间、最少路径等)。事实上,最终计算完成后,我们已经A得到了每个节点的最短路径;
  • 思路是:

    (1)找到“最便宜”的节点,即最短时间可以到达的节点。

    (2)更新该节点对应的邻居节点的cost,其含义稍后介绍。

    (3) 重复此过程,直到对图中的每个节点都完成此操作。

    (4) 计算最后一条路径。

    案例

    案例一

    我们举一个简单的例子来介绍一下其实现的思路:

    如下图,得到从起点到终点的最短路径。算法思想狄克斯特拉算法案例、局限性及java代码实现

    1.首先列出每个节点花费的起点和终点时间:

    父节点 节点 花费的时间
    起点
    出口点 B2分钟。 .终点∞被视为无穷大

    2。获取最便宜的节点。从上表我们知道起点->B成本最低。计算B节点到各个邻居节点所花费的时间,更新耗时较多的原节点:

    父节点节点耗时B5分钟更新时间,本来已经持续了6分钟3。此时B就被添加到已处理列表中,不再进行处理。现在至少还剩下起点->E成本,计算节点A走到所有邻居节点所需要的时间,并更新原来花费时间较多的节点:
    父节点节点耗时
    -A5分钟更新,本来需要6分钟
    起点 6分钟更新,本来花了7分钟

    4。这时,我们可以通过倒序知道:起点->A->终点,这条路径是最短路径,耗时6分钟

    情况2

    这里是前面广度优先搜索的例子算法:狄克斯特拉算法案例、局限性及java代码实现

    如上图所示,可以利用广度优先搜索算法来求从A到H的最小步数。路径为:A->B->E->H。这次将每条路线所需的时间相加(当然也可以代表公里数)作为每条路段的权重。

    现在A->B->E->H显然不再是最短路径。这个通过添加权重来寻找最短路径的问题可以用Dijkstra算法来解决:

    1。获取A在各个节点上花费的时间,A标记为已处理:

    父节点节点耗时
    A
    A ​​ C1 分钟。 ..H∞为无穷大

    2。找到A能到达的最短节点,并计算更新相邻节点所需的时间。这是最短的一个。 5分钟

    AC1分钟 C已处理
    CD

    F

    7分钟

    ... H

    ∞ 为无穷大 2. 找到 A 可以到达的最短节点,并计算更新相邻节点所需的时间。此时最短节点为B,B标记为已处理
    父节点节点耗时标记5分钟 B 处理
    A C 1 分钟 C 处理
    CD6 CF7 分钟
    B E15 分钟
    .. H∞ 被视为无穷大∞。 A的邻接节点已被处理。此时,求C能到达的最短节点。此时是C->D。计算更新相邻节点所需的时间。 D 标记为已处理
    父节点 节点 具有 标志
    A B 已处理
    A C 1 分钟 已处理
    CD6分钟DDF7分钟
    DE9 分钟
    ...H ∞ 是视为无穷大

    4. 以同样的方式更新父节点

    节点 标志
    A B已处理
    C C C 已处理 1 分钟 D G9 分钟 ∞ 被视为无穷大

    4。同样更新E

    父节点节点花费时间标志
    A 5分钟B 已处理
    AC 1 分钟。 F7分钟F处理
    D9分钟12分钟

    5.同样更新G

    父节点节点耗时 标志
    AB 5分钟B已处理
    AC 1 分钟 C已处理
    CD6 分钟

    D F

    7 分钟F 已处理D E 9 分钟 E 变成处理完毕,G->H并没有降低成本,所以更新H需要EH12分钟

    经过上述步骤,我们可以逆序得到最短路径:

    A->H。最短路线为:A->C->D->E->H,共需要12分钟;表中的其余结果也是最短的。路径,如A->G最短路径:A->C->F->G

    局限性

    该算法不适合负权重。情况3:

    狄克斯特拉算法案例、局限性及java代码实现

    这里我们用这个算法来看看为什么不合适,计算A->E:

    1。首先从起点A开始

    父节点节点耗时
    AB00 A C1元..终点 ∞ 被视为无穷大

    2。计算最便宜的节点C

    parentnodenode耗时logo
    AB100元
    A C100元C已处理完毕。 cd2元
    ...e点无限

    2.计算最便宜的节点B

    父节点节点耗时标志
    ABA100元B 已处理 C

    C

    -100元C已经处理完毕,但这里还需要更新。

    端点 ​​∞ 被视为无穷大

    3。最便宜节点D

    父节点节点计算需要LOGO
    AA100元 B 已处理
    B C-100元C已处理完毕,但这里还需要更新C
    C D♶和2 DE3元

    Dijkstra算法计算出的结果会是错误的A->C->D->E,花费3元。在Dijkstra算法中标记为已处理的节点意味着该节点没有其他更新。更便宜的方法。由于负权重的引入,这个原理不成立。在上述情况下,由于节点C已经被处理,所以C的内容必须再次更新,但相应地,C的后续子节点例如D可能存在。更新不同步,导致最终结果不正确。当然,也有可能计算是正确的。例如,如果上述第二种情况改为A->B = -50,则正确的最短路径可以计算为A --> B --> E --> H,成本为-37。

    个人认为能不能计算正确,就看权重为负的节点是否再次更新,以及相邻节点是否还没有计算完毕。例如案例 3 B 先处理 C:

    处理 B:

    父节点节点 耗时 B100元 B 加工中 A C -100元
    ..

    处理C:

    父节点节点耗时标志
    AB100元加工 AC-100元C已采购
    CD-99元
    ..端点

    端点在♿♿ at♿at

    父节点 节点 耗时 标志
    AB 10 0元 A C 已处理 已处理
    C D-99元D已处理
    DE♿E❿此时,正确的路径是A->->C->D->E,赚98元

    但这与Dijkstra相反,在某些情况下可能效率极低。所有 Dijkstra 算法都不应该与负权重一起使用。

    总结

    1.广度优先算法BFS主要适用于在无权有向图中重新搜索步数最少的路径。如果方向图中有权重,则不再适用

    2。迪杰斯特拉算法 Dijkstra 主要用于搜索带权有向图中的最短路径,但不适合权重为负的情况。对于环形图形,我个人的感觉和BFS是一样的。已处理的按钮被标记以避免进入无限循环。可以支持

    3.算法的实现主要是为了避免缺乏更好的解,采用穷举法来解决问题。当节点数量极其庞大时,算法的优势凸显出来

    Java实现

    /**
     * 狄克斯特拉算法
     * @author Administrator
     *
     */
    public class Dijkstra {
    	public static void main(String[] args){
    		HashMap<String,Integer> A = new HashMap<String,Integer>(){
    			{
    				put("B",5);
    				put("C",1);
    			}
    		};
    		
    		HashMap<String,Integer> B = new HashMap<String,Integer>(){
    			{
    				put("E",10);
    			}
    		};
    		HashMap<String,Integer> C = new HashMap<String,Integer>(){
    			{
    				put("D",5);
    				put("F",6);
    			}
    		};
    		HashMap<String,Integer> D = new HashMap<String,Integer>(){
    			{
    				put("E",3);
    			}
    		};
    		HashMap<String,Integer> E = new HashMap<String,Integer>(){
    			{
    				put("H",3);
    			}
    		};
    		HashMap<String,Integer> F = new HashMap<String,Integer>(){
    			{
    				put("G",2);
    			}
    		};
    		HashMap<String,Integer> G = new HashMap<String,Integer>(){
    			{
    				put("H",10);
    			}
    		};
    		HashMap<String,HashMap<String,Integer>> allMap = new HashMap<String,HashMap<String,Integer>>() {
    			{
    				put("A",A);
    				put("B",B);
    				put("C",C);
    				put("D",D);
    				put("E",E);
    				put("F",F);
    				put("G",G);
    			}
    		};
    		
    		
    		Dijkstra dijkstra = new Dijkstra();
    		dijkstra.handle("A","H",allMap);
    	}
    	
    	private String  getMiniCostKey(HashMap<String,Integer> costs,List<String> hasHandleList) {
    		int mini = Integer.MAX_VALUE;
    		String miniKey = null;
    		for(String key : costs.keySet()) {
    			if(!hasHandleList.contains(key)) {
    				int cost = costs.get(key);
    				if(mini > cost) {
    					mini = cost;
    					miniKey = key;
    				}
    			}
    		}
    		return miniKey;
    	}
    	
    	private void handle(String startKey,String target,HashMap<String,HashMap<String,Integer>> all) {
    		//存放到各个节点所需要消耗的时间
    		HashMap<String,Integer> costMap = new HashMap<String,Integer>();
    		//到各个节点对应的父节点
    		HashMap<String,String> parentMap = new HashMap<String,String>();
    		//存放已处理过的节点key,已处理过的不重复处理
    		List<String> hasHandleList = new ArrayList<String>();
    		
    		//首先获取开始节点相邻节点信息
    		HashMap<String,Integer> start = all.get(startKey);
    				
    		//添加起点到各个相邻节点所需耗费的时间等信息
    		for(String key:start.keySet()) {
    			int cost = start.get(key);
    			costMap.put(key, cost);
    			parentMap.put(key,startKey);
    		}
    		
    		
    		//选择最"便宜"的节点,这边即耗费时间最低的
    		String minCostKey = getMiniCostKey(costMap,hasHandleList);
    		while( minCostKey!=null ) {
    			System.out.print("处理节点:"+minCostKey);
    			HashMap<String,Integer> nodeMap = all.get(minCostKey);
    			if (nodeMap!=null) {
    				//该节点没有子节点可以处理了,末端节点
    				handleNode(minCostKey,nodeMap,costMap,parentMap);
    			}
    			//添加该节点到已处理结束的列表中
    			hasHandleList.add(minCostKey);
    			//再次获取下一个最便宜的节点
    			minCostKey = getMiniCostKey(costMap,hasHandleList);
    		}
    		if(parentMap.containsKey(target)) {
    			System.out.print("到目标节点"+target+"最低耗费:"+costMap.get(target));
    			List<String> pathList = new ArrayList<String>();
    			String parentKey = parentMap.get(target);
    			while (parentKey!=null) {
    				pathList.add(0, parentKey);
    				parentKey = parentMap.get(parentKey);
    			}
    			pathList.add(target);
    			String path="";
    			for(String key:pathList) {
    				path = path + key + " --> ";
    			}
    			System.out.print("路线为"+path);
    		} else {
    			System.out.print("不存在到达"+target+"的路径");
    		}
    	}
    	
    	private void handleNode(String startKey,HashMap<String,Integer> nodeMap,HashMap<String,Integer> costMap,HashMap<String,String> parentMap) {
    	
    		for(String key : nodeMap.keySet()) {
    			//获取原本到父节点所需要花费的时间
    			int hasCost = costMap.get(startKey);
    			//获取父节点到子节点所需要花费的时间
    			int cost = nodeMap.get(key);
    			//计算从最初的起点到该节点所需花费的总时间
    			cost = hasCost + cost;
    			
    			if (!costMap.containsKey(key)) {
    				//如果原本并没有计算过其它节点到该节点的花费
    				costMap.put(key,cost);
    				parentMap.put(key,startKey);
    			}else {
    				//获取原本耗费的时间
    				int oldCost = costMap.get(key);
    				if (cost < oldCost) {
    					//新方案到该节点耗费的时间更少
    					//更新到达该节点的父节点和消费时间对应的散列表
    					costMap.put(key,cost);
    					parentMap.put(key,startKey);
    					System.out.print("更新节点:"+key + ",cost:" +oldCost + " --> " + cost);
    				}
    			}
    		}
    	}
    复制代码
    执行完main方法打印信息如下:
    
        处理节点:C
        处理节点:B
        处理节点:D
        更新节点:E,cost:15 --> 9
        处理节点:F
        处理节点:E
        处理节点:G
        处理节点:H
        到目标节点H最低耗费:12路线为A --> C --> D --> E --> H 

    版权声明

    本文仅代表作者观点,不代表Code前端网立场。
    本文系作者Code前端网发表,如需转载,请注明页面地址。

    热门