dijkstra的经典的单源最短路径算法,用于求解无负边权的最短(最长)路问题。主要特点是从起点开始,采用贪心方法的策略,每次遍历到离源点最近且未访问过的邻接节点,直到扩张到终点为止。
1.初始化dist数组为INF(dist[i]表示源点到i的最短距离)
2.预处理,将dist[s]设为0,自己到自己的距离为0,且将s点与其邻接点的距离更新到dist数组中
3.从未收录的结点中选择距离最近结点的收录
4.将s点与收录点的所有未邻接点的距离更新(贪心)
朴素dijkstra
1.当图中节点较少(n <= 1000),边数(m)较多时,适合采用邻接表存图(如果用邻接矩阵存图,需要初始化为INF,表示两点不可达)
代码如下:
#include
#include
#include
#include
const int N = 1010, INF = 1e9;
int dist[N];
int g[N][N];
bool st[N];
int n, m;
//求源点s到其它点的最短路径
//简短求法
void dijkstra(int s){
memset(st, false, sizeof(st));
for(int i = 1; i <= n; i++) dist[i] = INF;
dist[s] = 0;
st[s] = true;
for(int i = 2; i <= n; i++){
int mind = INF, v = s;
for(int j = 1; j <= n; j++){
if(!st[j] && dist[j] < mind){
mind = dist[j];
v = j;
}
}
st[v] = true;
for(int j = 1; j <= n; j++) dist[j] = fmin(dist[j], dist[v] + g[v][j]);
}
}
//标准求法
void Dijkstra(int s){
memset(st, false, sizeof(st));
//预处理
for(int i = 1; i <= n; i++) dist[i] = g[s][i];
dist[s] = 0;
st[s] = true;
while(true){
int v = -1, mind = INF;
for(int i = 1; i <= n; i++){
if(!st[i] && dist[i] < mind){
mind = dist[i];
v = i;
}
}
if(v == -1) break;
st[v] = true;
for(int i = 1; i <= n; i++){
if(!st[i] && g[v][i] != INF && dist[i] > dist[v] + g[v][i])
dist[i] = dist[v] + g[v][i];
}
}
}
int main(void){
int a, b, c;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
g[i][j] = INF;
}
}
for(int i = 0; i < m; i++){
scanf("%d %d %d", &a, &b, &c);
g[a][b] = fmin(g[a][b], c); //如果有重边,选择权值小的边
}
dijkstra(1);
if(dist[n] == INF) puts("-1");
else printf("%d\n", dist[n]);
return 0;
}
2.当图的结点较多时,需要使用邻接表存图(由于指针表示的邻接表代码较多,这里用数组来模拟邻接表),使用邻接表不用担心重边问题,因为邻接边都会被更新到
代码如下:
#include
#include
#include
const int N = 1e6, M = 2e6;
int dist[N];
int h[N], to[M], w[M], ne[M], idx;
bool st[N];
int n, m;
void add(int a, int b, int c){
to[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra(int s){
for(int i = h[s]; ~i; i = ne[i]) dist[to[i]] = fmin(dist[to[i]], w[i]);
dist[s] = 0;
st[s] = true;
while(true){
int mind = 0x3f3f3f3f, v = -1;
for(int i = 1; i <= n; i++){
if(!st[i] && dist[i] < mind){
mind = dist[i];
v = i;
}
}
if(v == -1) break;
st[v] = true;
for(int i = h[v]; ~i; i = ne[i]){
dist[to[i]] = fmin(dist[to[i]], dist[v] + w[i]);
}
}
}
int main(void){
int a, b, c;
scanf("%d %d", &n, &m);
memset(dist, 0x3f, sizeof(dist));
memset(h, -1, sizeof(h));
for(int i = 0; i < m; i++){
scanf("%d %d %d", &a, &b, &c);
add(a, b, c);
}
dijkstra(1);
if(dist[n] == 0x3f3f3f3f) puts("-1");
else printf("%d\n", dist[n]);
return 0;
}
时间复杂度: O ( n m ) O(nm) O(nm)
堆优化的dijkstra
我们可以发现,dijkstra算法的第三步我们可以继续优化,不用每次都遍历n个结点,用堆这个数据构可以实现O(1)得到离源点距离最近的结点,O(logn)的时间复杂度插入结点,堆结点除了距离我们还要标识一下对应的结点标号,如果取出的点已经被收录,需要继续取结点
代码如下:
#include
#include
#include
const int N = 1e6, M = 2e6;
int dist[N];
int h[N], to[M], w[M], ne[M], idx;
bool st[N];
int heap[M][2];
int re[2]; //分别存从堆中取的点和距离
int n, m, hsize;
void add(int a, int b, int c){
to[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void push(int v, int d){
int i;
for(i = ++hsize; heap[i / 2][1] > d; i /= 2){
heap[i][0] = heap[i / 2][0];
heap[i][1] = heap[i / 2][1];
}
heap[i][0] = v;
heap[i][1] = d;
}
void pop(){
re[0] = heap[1][0];
re[1] = heap[1][1];
int lastv = heap[hsize][0];
int lastd = heap[hsize--][1];
int i, child;
for(i = 1; i * 2 <= hsize; i = child){
child = i * 2;
if(child != hsize && heap[child + 1][1] < heap[child][1]) child++;
if(lastd > heap[child][1]){
heap[i][0] = heap[child][0];
heap[i][1] = heap[child][1];
}
else break;
}
heap[i][0] = lastv;
heap[i][1] = lastd;
}
void dijkstra(int s){
dist[s] = 0;
push(s, 0);
while(hsize > 0){
pop();
int v = re[0], dis = re[1];
if(st[v]) continue;
st[v] = true;
for(int i = h[v]; ~i; i = ne[i]){
if(!st[to[i]] && dis + w[i] < dist[to[i]]){
dist[to[i]] = dis + w[i];
push(to[i], dist[to[i]]);
}
}
}
}
int main(void){
int a, b, c;
scanf("%d %d", &n, &m);
h[0] = 0x3f3f3f3f;
memset(dist, 0x3f, sizeof(dist));
memset(h, -1, sizeof(h));
for(int i = 0; i < m; i++){
scanf("%d %d %d", &a, &b, &c);
add(a, b, c);
}
dijkstra(1);
if(dist[n] == 0x3f3f3f3f) puts("-1");
else printf("%d\n", dist[n]);
return 0;
}
时间复杂度: O ( m l o g n ) O(mlogn) O(mlogn)
例题:
参考代码(1):
结构体指针实现的邻接表
typedef struct LGraph LGraph;
struct LGraph{
int V;
int T;
LGraph* next;
};
//头插法插入邻接点
void InertVertex(LGraph* obj, int pos, int v, int time){
LGraph* tmp = (LGraph*)malloc(sizeof(LGraph));
tmp->V = v;
tmp->T = time;
tmp->next = obj[pos].next;
obj[pos].next = tmp;
}
//初始化邻接表
LGraph* BuildGraph(int NumVertex){
LGraph* obj = (LGraph*)calloc(NumVertex, sizeof(LGraph)); //结构体数组
for(int i = 0; i < NumVertex; i++){
obj[i].V = i;
obj[i].next = NULL;
}
return obj;
}
//返回未被收录顶点中dist最小者
int FindMinDist(int n, int* dist, bool* collected){
int MinDist = INT_MAX;
int MinV;
for(int i = 1; i < n; i++){
if(collected[i] == false && dist[i] < MinDist){
MinDist = dist[i];
MinV = i;
}
}
if(MinDist == INT_MAX) return -1;
else return MinV;
}
int networkDelayTime(int** times, int timesSize, int* timesColSize, int N, int K){
LGraph* graph = BuildGraph(N + 1);
for(int i = 0; i < timesSize; i++)
InertVertex(graph, times[i][0], times[i][1], times[i][2]);
int* dist = (int*)calloc(N + 1, sizeof(int));
for(int i = 0; i < N + 1; i++) dist[i] = INT_MAX;
bool* collected = (bool*)calloc(N + 1, sizeof(bool));
memset(collected, 0, (N + 1) * sizeof(bool));
//对dist数组进行预处理
LGraph* p = (&graph[K])->next;
while(p != NULL){
dist[p->V] = p->T;
p = p->next;
}
//起点收入集合
dist[K] = 0;
collected[K] = true;
//Dijkstra算法
while(1){
int v = FindMinDist(N + 1, dist, collected);
if(v == -1) break;
collected[v] = true;
p = (&graph[v])->next;
while(p != NULL){
if(collected[p->V] == false && dist[v] + p->T < dist[p->V])
dist[p->V] = dist[v] + p->T;
p = p->next;
}
}
//遍历dist数组,若无未访问的节点,返回最大值
int delaytime = -1;
for(int i = 1; i < N + 1; i++){
if(dist[i] == INT_MAX){
delaytime = -1;
break;
}
else delaytime = delaytime < dist[i] ? dist[i] : delaytime;
}
//释放内存
free(graph);
free(dist);
free(collected);
return delaytime;
}
参考代码(2):
邻接矩阵实现
int n;
#define MAXN 105
const int INF = 0x3f;
int g[MAXN][MAXN], dist[MAXN];
bool st[MAXN];
void dijkstra(int s){
for(int i = 1; i <= n; i++) dist[i] = g[s][i];
dist[s] = 0;
st[s] = true;
int v = s;
for(int i = 1; i <= n; i++){
int mind = 0x3f3f3f3f;
for(int j = 1; j <= n; j++){
if(!st[j] && dist[j] < mind){
mind = dist[j];
v = j;
}
}
st[v] = true;
for(int j = 1; j <= n; j++){
if(dist[v] + g[v][j] < dist[j])
dist[j] = dist[v] + g[v][j];
}
}
}
int networkDelayTime(int** times, int timesSize, int* timesColSize, int N, int K){
int u, v, w;
n = N;
memset(g, INF, sizeof(g));
memset(dist, INF, sizeof(dist));
memset(st, false, sizeof(st));
for(int i = 0; i < timesSize; i++){
u = times[i][0], v = times[i][1], w = times[i][2];
g[u][v] = w;
}
dijkstra(K);
int res = 0;
for(int i = 1; i <= N; i++){
if(dist[i] == 0x3f3f3f3f)
return -1;
else res = fmax(res, dist[i]);
}
return res;
}
2.LeetCode 1514.概率最大的路径
此题相当于是求最长路,且需要堆优化
参考代码:
//dijkstra + 堆优化
#define N 10010
#define M 100000
int h[N], to[M], ne[M], idx;
double w[M], dist[N], heap[M][2];
int heapSize;
int hv; //出堆结点标号
double hp; //源点到出堆结点的最大概率
bool st[N];
void push(int v, double p){
int i;
for(i = ++heapSize; heap[i / 2][0] < p; i /= 2){
heap[i][0] = heap[i / 2][0];
heap[i][1] = heap[i / 2][1];
}
heap[i][0] = p;
heap[i][1] = v;
}
void pop(){
hp = heap[1][0];
hv = heap[1][1];
double lastp = heap[heapSize][0];
int lastv = heap[heapSize--][1];
int i, child;
for(i = 1; i * 2 <= heapSize; i = child){
child = i * 2;
if(child != heapSize && heap[child + 1][0] > heap[child][0]) child++;
if(lastp < heap[child][0]){
heap[i][0] = heap[child][0];
heap[i][1] = heap[child][1];
}
else break;
}
heap[i][0] = lastp;
heap[i][1] = lastv;
}
void add(int a, int b, double c){
to[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra(int s, int n){
dist[s] = 1.0;
push(s, 1.0);
while(heapSize > 0){
pop();
if(st[hv]) continue;
st[hv] = true;
for(int i = h[hv]; ~i; i = ne[i]){
if(!st[to[i]] && hp * w[i] > dist[to[i]]){
dist[to[i]] = hp * w[i];
push(to[i], dist[to[i]]);
}
}
}
}
double maxProbability(int n, int** edges, int edgesSize, int* edgesColSize, double* succProb, int succProbSize, int start, int end){
int a, b;
double c;
idx = heapSize = 0;
heap[0][0] = 1.0; //哨兵结点
memset(st, false, sizeof(st));
memset(h, -1, sizeof(h));
for(int i = 0; i < n; i++) dist[i] = 0.0;
for(int i = 0; i < edgesSize; i++){
a = edges[i][0], b = edges[i][1], c = succProb[i];
add(a, b, c);
add(b, a, c);
}
dijkstra(start, n);
if(dist[end] == 0.0) return 0.0;
return dist[end];
}