【圖論】深入理解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] \rbrace \]

  • 重復上述操作\(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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM