跳到主要内容

Dijkstra

Dijkstra算法详解

Dijkstra算法

定义

Dijkstra算法是一种用于计算加权图中单源最短路径的贪心算法。给定一个图和一个起始顶点,该算法找到从起始顶点到其他所有顶点的最短路径。

操作步骤

  1. 初始化:将起始顶点的距离设为0,其他顶点的距离设为无穷大。
  2. 从未处理顶点中选择距离最小的顶点,将其标记为已处理。
  3. 更新所选顶点的邻接顶点的距离。
  4. 重复步骤2和3,直到所有顶点都被处理。

代码实现(C语言)

#include <stdio.h>
#include <limits.h>
#include <stdbool.h>

#define V 9 // 顶点数

// 查找最小距离的顶点
int minDistance(int dist[], bool sptSet[]) {
int min = INT_MAX, minIndex;

for (int v = 0; v < V; v++) {
if (sptSet[v] == false && dist[v] <= min) {
min = dist[v], minIndex = v;
}
}

return minIndex;
}

// 打印最短路径树
void printSolution(int dist[]) {
printf("Vertex \t Distance from Source\n");
for (int i = 0; i < V; i++) {
printf("%d \t\t %d\n", i, dist[i]);
}
}

// Dijkstra算法
void dijkstra(int graph[V][V], int src) {
int dist[V];
bool sptSet[V];

// 初始化
for (int i = 0; i < V; i++) {
dist[i] = INT_MAX, sptSet[i] = false;
}

// 起始顶点
dist[src] = 0;

// V-1次循环
for (int count = 0; count < V - 1; count++) {
// 寻找最短的那个
int u = minDistance(dist, sptSet);
// 加入最短路径集合中
sptSet[u] = true;

// 更新与刚刚加入的顶点相连的顶点的距离值
for (int v = 0; v < V; v++) {
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}

printSolution(dist);
}

int main() {
int graph[V][V] = {
{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 14, 10, 0, 2, 0, 0},
{0, 0, 0, 0, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}
};

dijkstra(graph, 0);

return 0;
}

3. 时间复杂度和空间复杂度分析

Dijkstra算法

  • 时间复杂度:使用邻接矩阵时为O(V2)O(V^2),使用邻接表和优先队列时为O(ElogV)O(E \log V)
  • 空间复杂度O(V)O(V),主要用于存储最短路径树和访问标记。

比较学习:Prim算法

Dijkstra和Prim算法是高度相似的,只差了几乎一行代码,所以可以拿来比较学习

Prim算法是一种逐步扩展的贪心算法,初始时从一个顶点开始,然后逐步添加最小权重的边,直到所有顶点都包含在树中。

步骤

  1. 选择一个起始顶点,将其加入最小生成树。
  2. 重复以下步骤,直到最小生成树包含所有顶点:
    • 在所有连接已包含顶点和未包含顶点的边中,选择权重最小的边。(这里是未链接的顶点链接到已链接的顶点的集合的权重最小的边,这里不是连接到起始顶点的边权重(这里与dijiesitela算法区分))
    • 将该边和相应的顶点加入最小生成树。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define V 5

// 在所有连接已包含顶点和未包含顶点的边中,选择权重最小的边
//这里通过更新与加入已包含顶点的集合顶点所相连的边来寻找在所有连接已包含顶点和未包含顶点的边
int minKey(int key[], int mstSet[]) {
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++)
if (mstSet[v] == 0 && key[v] < min)
min = key[v], min_index = v;
return min_index;
}


//打印
void printMST(int parent[], int graph[V][V]) {
printf("Edge \tWeight\n");
for (int i = 1; i < V; i++)
printf("%d - %d \t%d \n", parent[i], i, graph[i][parent[i]]);
}

void primMST(int graph[V][V]) {

//graph[V][V]保存边的权重值

int parent[V]; //前驱节点
int key[V]; //当前连接到最小生成树集合的权重
int mstSet[V]; //是否已经加入最小生成树集合

//初始化
for (int i = 0; i < V; i++)
key[i] = INT_MAX, mstSet[i] = 0;

key[0] = 0;
parent[0] = -1;

//Prim
for (int count = 0; count < V - 1; count++) {
//寻找连接权重最小的边的点
int u = minKey(key, mstSet);

//加入到最小生成树集合
mstSet[u] = 1;

//更新与刚加入的点相连的点
for (int v = 0; v < V; v++)
if (graph[u][v] && mstSet[v] == 0 && graph[u][v] < key[v])
parent[v] = u, key[v] = graph[u][v];
}

printMST(parent, graph);
}

int main() {
int graph[V][V] = {{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}};
primMST(graph);
return 0;
}