zoukankan      html  css  js  c++  java
  • 【图论】深入理解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.

  • 相关阅读:
    Java 8 Lambda 表达式
    OSGi 系列(十二)之 Http Service
    OSGi 系列(十三)之 Configuration Admin Service
    OSGi 系列(十四)之 Event Admin Service
    OSGi 系列(十六)之 JDBC Service
    OSGi 系列(十)之 Blueprint
    OSGi 系列(七)之服务的监听、跟踪、声明等
    OSGi 系列(六)之服务的使用
    OSGi 系列(三)之 bundle 事件监听
    OSGi 系列(三)之 bundle 详解
  • 原文地址:https://www.cnblogs.com/en-heng/p/3941759.html
Copyright © 2011-2022 走看看