《算法图解》-7狄克斯特拉算法

本文属于《算法图解》系列。

一 狄克斯特拉算法

      前一章,我们使用了广度优先的算法来查找两点之间的最短距离,那时的“最短距离”是指路径最少,在狄克斯特拉算法中,你给每段都分配了一个数字或权重,因此狄克斯特拉算法找出的是总权重最小的路径。

    狄克斯特拉算法包含4个步骤。
(1) 找出最便宜的节点,即可在最短时间内前往的节点。
(2) 对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销。
(3) 重复这个过程,直到对图中的每个节点都这样做了。
(4) 计算最终路径。

你可能觉得看起来简单,但是如果从百度百科上看数学原理,还是很复杂的。

下面来看看如何对下面的图使用这种算法。

第一步:找出最便宜的节点。你站在起点,不知道该前往节点A还是前往节点B。前往这两个节点都要多长时间呢?

  

前往节点A需要6分钟,而前往节点B需要2分钟。由于你还不知道前往终点需要多长时间,因此你假设为无穷大,节点B是最近的,2分钟。

第二步:计算经节点B前往其各个邻居所需的时间。

    

直接前往节点A需要6分钟。但经由节点B前往节点A只需5分钟。

对于节点B的邻居,如果找到前往它的更短路径,就更新其开销。在这里,你找到了:

前往节点A的更短路径(时间从6分钟缩短到5分钟);

前往终点的更短路径(时间从无穷大缩短到7分钟)。

第三步:重复!

   重复第一步:找出可在最短时间内前往的节点。你对节点B执行了第二步,除节点B外,可在最短时间内前往的节点是节点A

 

重复第二步:更新节点A的所有邻居的开销。

你发现前往终点的时间为6分钟!

你对每个节点都运行了狄克斯特拉算法(无需对终点这样做)。现在,你知道:

前往节点B需要2分钟;

前往节点A需要5分钟;

前往终点需要6分钟。

那么最终路径为:

  

术语

狄克斯特拉算法用于每条边都有关联数字的图,这些数字称为权重weight)。

带权重的图称为加权图weighted graph),不带权重的图称为非加权图unweighted graph)。

图还可能有环,而 环类似下面这样。

在无向图中,每条边都是一个环。狄克斯特拉算法只适用于有向无环图(directed acyclic graphDAG)。

二 负权边

作者还介绍了一个以物换物,最小成本换到目标的例子,乐谱换钢琴

前面使用的都是术语最短路径的字面意思:计算两点或两人之间的最短路径。但希望这个示例让你明白:最短路径指的并不一定是物理距离,也可能是让某种度量指标最小。在这个示例中,最短路径指的是想要额外支付的费用最少。

如果有负权边,就不能使用狄克斯特拉算法

三 实现

     要编写解决这个问题的代码,需要三个散列表。

好吧,又一次见证了Python的强大。可能书上作者的几行代码,用Java就得写一坨。

下面表示边对应的权重。

开销表的代码如下

存储对应父节点关系,(找路径使用)

  

基础准备做好了,看下算法步骤:

好了,到此为止,已经介绍完了,我试着用Java 写一版。明显不如Python简洁。我觉得永Java的二维数组来标识,不是那么直观的表示出散列表的内容。所以跟上一章类似,还是用hashmap实现。

  

/**
 * 
 * @author bohu83
 *
 */
public class DijkstraTest {
	
	static HashMap<String,Integer> start = new HashMap<String,Integer>();
	static HashMap<String,Integer> a = new HashMap<String,Integer>();
	static HashMap<String,Integer> b = new HashMap<String,Integer>();
	//这里对应图上graph
	static HashMap<String,HashMap<String,Integer>> allMap = new HashMap<String,HashMap<String,Integer>>(); 
		
	static{
	 //准备数据
	 start.put("a", 6);
	 start.put("b", 2);
	 a.put("end", 1);
	 b.put("a", 3);
	 b.put("end", 5);
	 allMap.put("a", a);
	 allMap.put("b", b);
	 allMap.put("start", start);
	}
	
	private static 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);
        }
        costMap.put("start", Integer.MAX_VALUE);
        costMap.put("end", Integer.MAX_VALUE);
		
        //选择最"便宜"的节点,这边即耗费时间最低的
        String lowestCostKey = getLowestCostKey(costMap,hasHandleList);
       
        while (lowestCostKey!=null ){
        	
        	//获取neighbor
        	 HashMap<String,Integer> nodeMap = all.get(lowestCostKey);
        	 if(nodeMap != null){
        		 
        		 for(String key : nodeMap.keySet()) {
        				//获取该节点要花费的时间
        				int hasCost = costMap.get(lowestCostKey);
        				//获取该到另一节点所需要花费的时间
        				int cost = nodeMap.get(key);
        				//计算从最初的起点到该节点所需花费的总时间
        				cost = hasCost + cost;        				
        			
    					//获取原本耗费的时间
    					int oldCost = costMap.get(key);
    					if (cost < oldCost) {
    						//新方案到该节点耗费的时间更少
    						//更新到达该节点的父节点和消费时间对应的散列表
    						costMap.put(key,cost);
    						parentMap.put(key,lowestCostKey);
    						System.out.println("更新节点:"+key + ",parent:-->"+lowestCostKey+",cost:" +oldCost + " --> " + cost);
    					}        				
        				
        		 } 
        		 
        	 }
        	 //添加该节点到已处理结束的列表中
             hasHandleList.add(lowestCostKey);
             //再次获取下一个最便宜的节点
             lowestCostKey = getLowestCostKey(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 static String getLowestCostKey(HashMap<String, Integer> costMap, List<String> hasHandleList) {
		
		int mini = Integer.MAX_VALUE;
        String miniKey = null;
        for(String key : costMap.keySet()) {
            if(!hasHandleList.contains(key)) {
                int cost = costMap.get(key);
                if(mini > cost) {
                    mini = cost;
                    miniKey = key;
                }
            }
        }
        System.out.println("lowest cost key="+miniKey);
        return miniKey;
	}


	public static void main(String[] args) {
		
		handle("start","end",allMap);
	}

}

 运行结果如下:

lowest cost key=b
更新节点:a,parent:-->b,cost:6 --> 5
更新节点:end,parent:-->b,cost:2147483647 --> 7
lowest cost key=a
更新节点:end,parent:-->a,cost:7 --> 6
lowest cost key=end
lowest cost key=null
到目标节点end最低耗费:6路线为start --> b --> a --> end --> 

其实自己动手写一下,更容易理解。为了便于理解,贴一下书上的图。

找出开销最低的节点。

获取该节点的开销和邻居。

每个节点都有开销。开销指的是从起点前往该节点需要多长时间。在这里,你计算从起点出 发,经节点B前往节点A(而不是直接前往节点A)需要多长时间。

找到了一条前往节点A的更短路径!因此更新节点A的开销。

下一个邻居是终点节点。 经节点B前往终点需要多长时间呢?

                         

需要7分钟。终点原来的开销为无穷大,比7分钟长。

                                

设置终点节点的开销和父节点。

                                

你更新了节点B的所有邻居的开销。现在,将节点B标记为处理过。

                         

找出接下来要处理的节点。

                              

获取节点A的开销和邻居。

                                

节点A只有一个邻居:终点节点。 当前,前往终点需要7分钟。如果经节点A前往终点,需要多长时间呢?

                           

 经节点A前往终点所需的时间更短!因此更新终点的开销和父节点。

                            

处理所有的节点后,这个算法就结束了。

 

 

 

 

 

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值