• 【图论】深入理解Dijsktra算法


    1. 介绍

    Dijsktra算法是大牛Dijsktra于1956年提出,用来解决有向图单源最短路径问题;但是不能解决负权的有向图,若要解决负权图则需要用到Bellman-Ford算法。Dijsktra算法思想:在DFS遍历图的过程中,每一次取出离源点的最近距离的点,将该点标记为已访问,松弛与该点相邻的结点。

    有向图记为(G=(n, m)),其中,(n)为顶点数,(m)为边数;且(e[a,b])表示从结点(a)到结点(b)的边。(d[i])记录源点到结点i的距离,(U)为未访问的结点集合,(V)为已访问的结点集合。Dijsktra算法具体步骤如下:

    • 从集合(U)中寻找离源点最近的结点(u),并将结点(u)标记为已访问(从集合(U)中移到集合(V)中)

    [u = mathop {arg min } limits_{i in U} d[i] ]

    • 松弛与结点(u)相邻的未访问结点,更新d数组

    [mathop {d[i]} limits_{i in U} = min lbrace d[i] , d[u] + e[u,i] brace ]

    • 重复上述操作(n)次,即访问了所有结点,集合(U)为空

    Dijsktra算法的Java实现

    /**
     * Dijkstra's Algorithm for finding the shortest path
     *
     * @param adjMatrix adjacency matrix representation of the graph
     * @param source    the source vertex
     * @param dest      the destination vertex
     * @return the cost for the shortest path
     */
    public static int dijkstra(int[][] adjMatrix, int source, int dest) {
      int numVertex = adjMatrix.length, minVertex = source;
      // `d` marks the cost for the shortest path, `visit` marks whether has been visited or not
      int[] d = new int[numVertex], visit = new int[numVertex];
      Arrays.fill(d, Integer.MAX_VALUE);
      d[source] = 0;
      for (int cnt = 1; cnt <= numVertex; cnt++) {
        int lowCost = Integer.MAX_VALUE;
        // find the min-vertex which is the nearest among the unvisited vertices
        for (int i = 0; i < numVertex; i++) {
          if (visit[i] == 0 && d[i] < lowCost) {
            lowCost = d[i];
            minVertex = i;
          }
        }
        visit[minVertex] = 1;
        if (minVertex == dest) return d[dest];
        // relax the minVertex's adjacency vertices
        for (int i = 0; i < numVertex; i++) {
          if (visit[i] == 0 && adjMatrix[minVertex][i] != Integer.MAX_VALUE) {
            d[i] = Math.min(d[i], d[minVertex] + adjMatrix[minVertex][i]);
          }
        }
      }
      return d[dest];
    }
    

    复杂度分析

    • 时间复杂度:重复操作(即最外层for循环)n次,找出minNode操作、松弛操作需遍历所有结点,因此复杂度为(O(n^2)).
    • 空间复杂度:开辟两个长度为n的数组d与visit,因此复杂度为(T(n)).

    2. 优化

    从上述Java实现中,我们发现:(里层for循环)寻找距离源点最近的未访问结点(u),通过遍历整个数组来实现的,缺点是重复访问已经访问过的结点,浪费了时间。首先,我们来看看堆的性质。

    堆是一种完全二叉树(complete binary tree);若其高度为h,则1~h-1层都是满的。如果从左至右从上至下从1开始给结点编号,堆满足:

    • 结点(i)的父结点编号为(i/2).
    • 结点(i)的左右孩子结点编号分别为(2*i), (2*i+1).

    如果结点(i)的关键值小于父结点的关键值,则需要进行上浮操作(move up);如果结点(i)的关键值大于父结点的,则需要下沉操作(move down)。为了保持堆的整体有序性,通常下沉操作从根结点开始。

    小顶堆优化Dijsktra算法

    我们可以用小顶堆来代替d数组,堆顶对应于结点(u);取出堆顶,然后删除,如此堆中结点都是未访问的。同时为了记录数组(d[i])中索引(i)值,我们让每个堆结点挂两个值——顶点、源点到该顶点的距离。算法伪代码如下:

    Insert(vertex 0, 0)  // 插入源点
    FOR i from 1 to n-1:  // 初始化堆
        Insert(vertex i, infinity)
    
    FOR k from 1 to n:
        (i, d) := DeleteMin()
        FOR all edges ij:
            IF d + edge(i,j) < j’s key
                DecreaseKey(vertex j, d + edge(i,j))
    
    
    1. Insert(vertex i, d)指在堆中插入堆结点(i, d)。
    2. DeleteMin()指取出堆顶并删除,时间复杂度为(O(log n))
    3. DecreaseKey(vertex j, d + edge(i,j))是松弛操作,更新结点(vertex j, d + edge(i,j)),需要进行上浮,时间复杂度为(O(log n))

    我们需要n次DeleteMin,m次DecreaseKey,优化版的算法时间复杂度为(O((n+m)log n)).

    代码实现

    邻接表
    每一个邻接表的表项包含两个部分:头结点、表结点,整个邻接表可以用一个头结点数组表示。下面给出其Java实现

    public class AdjList {
    	private int V = 0;
    	private HNode[] adjList =null; //邻接表
    	
    	/*表结点*/
    	 class ArcNode {
    		int adjvex, weight;
    		ArcNode next;
    		
    		public ArcNode(int adjvex, int weight) {
    			this.adjvex = adjvex;
    			this.weight = weight;
    			next = null;
    		}
    	}
    	
    	 /*头结点*/
    	class HNode {
    		int vertex;
    		ArcNode firstArc;
    		
    		public HNode(int vertex) {
    			this.vertex = vertex;
    			firstArc = null;
    		}
    	}
    	
    	/*构造函数*/
    	public AdjList(int V) {
    		this.V = V;
    		adjList = new HNode[V+1];
    		for(int i = 1; i <= V; i++) {
    			adjList[i] = new HNode(i);
    		}
    	}
    	
    	/*添加边*/
    	public void addEdge(int start, int end, int weight) {
    		ArcNode arc = new ArcNode(end, weight);
    		ArcNode temp = adjList[start].firstArc;
    		adjList[start].firstArc = arc;
    		arc.next = temp;
    	}
    	
    	public int getV() {
    		return V;
    	}
    
    	public HNode[] getAdjList() {
    		return adjList;
    	}
    
    }
    

    小顶堆

    public class Heap {
    	public int size = 0 ;
    	public Node[] h = null;     //堆结点
    	
    	/*记录Node中vertex对应堆的位置*/
    	public int[] index = null;  
    	
    	/*堆结点:
    	 * 存储结点+源点到该结点的距离
    	 */
    	public class Node {
    		int vertex, weight;
    		
    		public Node(int vertex, int weight) {
    			this.vertex = vertex;
    			this.weight = weight;
    		}
    	}
    	
    	public Heap(int maximum) {
    		h = new Node[maximum];
    		index = new int[maximum];
    	}
    	
    	/*上浮*/
    	public void moveUp(int pos) {
    		Node temp = h[pos];
    		for (; pos > 1 && h[pos/2].weight > temp.weight; pos/=2) {
    			h[pos] = h[pos/2];
    			index[h[pos].vertex] = pos;  //更新位置
    		}
    		h[pos] = temp;
    		index[h[pos].vertex] = pos;
    	}
    	
    	/*下沉*/
    	public void moveDown( ) {
    		Node root = h[1];
    		int pos = 1, child = 1;
    		for(; pos <= size; pos = child) {
    			child = 2*pos;
    			if(child < size && h[child+1].weight < h[child].weight)
    				child++;
    			if(h[child].weight < root.weight) {
    				h[pos] = h[child];
    				index[h[pos].vertex] = pos;
    			} else {
    				break;
    			}
    		}
    		h[pos] = root;
    		index[h[pos].vertex] = pos;
    	}
    	
    	/*插入操作*/
    	public void insert(int v, int w) {
    		h[++size] = new Node(v, w);
    		moveUp(size);
    	}
    	
    	/*删除堆顶元素*/
    	public Node deleteMin( ) {
    		Node result = h[1];
    		h[1] = h[size--];
    		moveDown();
    		return result;
    	}
    
    }
    

    优化算法

    
    public class ShortestPath {
    	private static final int inf = 0xffffff;
    	
    	public static void dijkstra(AdjList al) {
    		int V = al.getV();
    		Heap heap = new Heap(V+1);
    		heap.insert(1, 0);
    		for(int i = 2; i <= V; i++) {
    			heap.insert(i, inf);
    		}
    		
    		for(int k =1; k <= V; k++) {
    			Heap.Node min = heap.deleteMin();
    			if(min.vertex == V) {
    				System.out.println(min.weight);
    				break;
    			}
    			AdjList.ArcNode arc = al.getAdjList()[min.vertex].firstArc;
    			while(arc != null) {
    				if((min.weight+ arc.weight) < heap.h[heap.index[arc.adjvex]].weight) {
    					heap.h[heap.index[arc.adjvex]].weight = min.weight+ arc.weight;
    					heap.moveUp(heap.index[arc.adjvex]);
    				}
    				arc = arc.next;
    			}
    		}
    	}
    	
    	/*main方法用于测试*/
    	public static void main(String[] args) {
    		AdjList al = new AdjList(5);
    		al.addEdge(1, 2, 20);
    		al.addEdge(2, 3, 30);
    		al.addEdge(3, 4, 20);
    		al.addEdge(4, 5, 20);
    		al.addEdge(1, 5, 100);
    		dijkstra(al);
    	}
    }
    

    3. 参考资料#

    [1] FRWMM, ALGORITHMS - DIJKSTRA WITH HEAPS.

  • 相关阅读:
    Python模块进阶、标准库、扩展库
    python垃圾回收机制
    VMWare workstation 安装 CentOS 8后自适应调整分辨率(如1920x1080)
    使用 Zeal 打造属于自己的文档
    Erlang 开发者的福音:IntelliJ IDEA 的 Erlang 插件
    Intellij IDEA 14的注册码
    在Intellij IDEA或者PhpStorm下用X-debug调试PHP
    PHPCMS 核心代码与 www 分离部署
    PHPCMS如何实现后台访问限制?
    推荐:PHPCMS v9 安全防范教程!
  • 原文地址:https://www.cnblogs.com/en-heng/p/3941759.html
Copyright © 2020-2023  润新知