dijkstra算法主要用来求稀疏图的单源点最短路径,其核心是首先存储单源点到其它各点的路径,叫做估计最短径值,然后找出其中最短的一条路径,记录该点,那么此时该点的估计最短径值就变成了确定最短路径值。
例如:这里同dis数组来记录单源点1到其它各点最短路径值,下标从1开始,最初其值为 0 5 1 3 4.
此时得到dis[3] = 1为最小值,则可以确定源点到点3的最短路径,另外点3到点2有一条路径长度为2,容易知道,1+2=3 > 5,则此时可松弛点2的最短路径变为3。此时大致的思路已经出来了。
先来一个邻接矩阵存储边信息的Dijkstra,
#include <iostream>
#include <cstdio>
#define inf 10e7
using namespace std;
int main(){
int a, b, c, k, i, j, n, m, l, min, jk[100][100], dis[100], book[100]={0};
cin >> n >> m;
for(i = 1; i <= n; ++i)
for(j = 1; j <= n; ++j)
if(i == j) jk[i][j] = 0;
else jk[i][j] = inf;
for(i = 1; i <= m; ++i){
cin >> a >> b >> c;
jk[a][b] = c;
}
for(i = 1; i <= n; ++i)
dis[i] = jk[1][i];
book[1] = 1;
for(k = 1; k < n; ++k){
min = inf;
for(i = 1; i <= n; ++i)
if(book[i] == 0 && min >= dis[i]) {
min = dis[i];
l = i;
}
book[l] = 1;
for(i = 1; i <= n; ++i)
if(jk[l][i] < inf && dis[i] > dis[l]+jk[l][i])
dis[i] = dis[l]+jk[l][i];
}
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
return 0;
}
再来一个数组实现的邻接表的Dijisra,
#include <iostream>
#include <cstring>
#include <cstdio>
#define inf 10e7
using namespace std;
int u[100], v[100], w[100], head[100], next2[100], dis[100], book[100]={0};
int main(){
int k, i, j, n, m, l, min;
cin >> n >> m;
memset(head, -1, sizeof(head));
for(i = 1; i <= m; ++i){
cin >> u[i] >> v[i] >> w[i];
next2[i] = head[u[i]];
head[u[i]] = i;
}
for(i = 1; i <= n; ++i)
dis[i] = inf;
dis[1] = 0;
k = head[1];
while(k != -1){
dis[v[k]] = w[k];
k = next2[k];
}
book[1] = 1;
for(j = 1; j < n; ++j){
min = inf;
for(i = 1; i <= n; ++i)
if(book[i] == 0 && min >= dis[i]) {
min = dis[i];
l = i;
}
book[l] = 1;
k = head[l];
while(k != -1){
if(w[k] < inf && dis[v[k]] > dis[l]+w[k])
dis[v[k]] = dis[l]+w[k];
k = next2[k];
}
}
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
return 0;
}
别小看了邻接表和邻接矩阵的差别,有时候题目是否可以AC就在于次,邻接矩阵空间复杂度为O(n^2),邻接表空间复杂度为O(M),对于稀疏图来说,两者的差距可想而知。
所以,再来一个类似邻接表的存储方式——前向星存储,它们的空间复杂度是差不多,
#include <iostream>
#include <cstring>
#include <cstdio>
#define inf 10e7
using namespace std;
int no, head[100], dis[100], book[100]={0};
struct node{
int to, w, next;
} edge[100];
void add(int u, int v, int w){
edge[no].to = v;
edge[no].w = w;
edge[no].next = head[u];
head[u] = no++;
}
int main(){
int a, b, c, k, i, j, n, m, l, min;
cin >> n >> m;
no = 1;
memset(head, -1, sizeof head);
for(i = 1; i <= m; ++i){
cin >> a >> b >> c;
add(a, b, c);
}
for(i = 1; i <= n; ++i)
dis[i] = inf;
dis[1] = 0;
k = head[1];
while(k != -1){
dis[edge[k].to] = edge[k].w;
k = edge[k].next;
}
book[1] = 1;
for(j = 1; j < n; ++j){
min = inf;
for(i = 1; i <= n; ++i)
if(book[i] == 0 && min >= dis[i]) {
min = dis[i];
l = i;
}
book[l] = 1;
k = head[l];
while(k != -1){
if(edge[k].w < inf && dis[edge[k].to] > dis[l]+edge[k].w)
dis[edge[k].to] = dis[l]+edge[k].w;
k = edge[k].next;
}
}
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
return 0;
}
既然我们学到了图论,想必已经学过单链表了,所以博主无聊又用链式邻接表来实现了一番,
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define inf 10e7
using namespace std;
struct node{
int v, w;
node *next;
}*jk[105], *k, *pre;
int dis[100], book[100];
int main(){
int n, m, u, v, w, min, i, j, l;
memset(book, 0, sizeof book);
cin >> n >> m;
for(i = 1; i <= n; ++i)
jk[i] = NULL;
for(i = 1; i <= m; ++i){
cin >> u >> v >> w;
k = jk[u];
pre = NULL;
while(k != NULL) {
pre = k;
k = k->next;
}
k = (struct node*)malloc(sizeof(struct node));
k->v = v;
k->w = w;
k->next = NULL;
if(pre == NULL) jk[u] = k;
else pre->next = k;
// k = jk[v]; //如果是无向图,应加上这么一块
// pre = NULL;
// while(k != NULL) {
// pre = k;
// k = k->next;
// }
// k = (struct node*)malloc(sizeof(struct node));
// k->v = w;
// k->w = w;
// k->next = NULL;
// if(pre == NULL) jk[v] = k;
// else pre->next = k;
}
for(i = 1; i <= n; ++i)
dis[i] = inf;
dis[1] = 0;
k = jk[1];
while(k != NULL){
dis[k->v] = k->w;
k = k->next;
}
book[1] = 1;
for(j = 1; j < n; ++j){
min = inf;
for(i = 1; i <= n; ++i)
if(book[i] == 0 && min >= dis[i]) {
min = dis[i];
l = i;
}
book[l] = 1;
k = jk[l];
while(k != NULL){
if(dis[k->v] > dis[l]+k->w)
dis[k->v] = dis[l]+k->w;
k = k->next;
}
}
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
return 0;
}
//如果是无向图,应加上这么一块
// pre = NULL;
// while(k != NULL) {
// pre = k;
// k = k->next;
// }
// k = (struct node*)malloc(sizeof(struct node));
// k->v = w;
// k->w = w;
// k->next = NULL;
// if(pre == NULL) jk[v] = k;
// else pre->next = k;
}
for(i = 1; i <= n; ++i)
dis[i] = inf;
dis[1] = 0;
k = jk[1];
while(k != NULL){
dis[k->v] = k->w;
k = k->next;
}
book[1] = 1;
for(j = 1; j < n; ++j){
min = inf;
for(i = 1; i <= n; ++i)
if(book[i] == 0 && min >= dis[i]) {
min = dis[i];
l = i;
}
book[l] = 1;
k = jk[l];
while(k != NULL){
if(dis[k->v] > dis[l]+k->w)
dis[k->v] = dis[l]+k->w;
k = k->next;
}
}
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
return 0;
}
最后,就是我们的终极形态了,前向星存储+堆优化的DIjistra,为什么要用到堆优化呢,因为我们每次寻找最短的路径值的时候,需要花费O(N)的时间去寻找,用堆可优化至O(logN),也可别小瞧这俩之间的差距。一提到这儿,我又想起了昨天的比赛,就有这么一道最短路的题,刚开始我和队友用的是队列优化的Bellman-ford算法即SPFA,博主比较喜欢这个算法,但昨天的比赛的数据点卡住SPFA了,昨天的题是在第49个点上TLE(超时)的,用堆优化的Dijistra才AC了那道题...博主才学疏浅,希望神牛不要嘲笑,好,下面贴出我们的代码,
#include <iostream>
#include <string.h>
#include <cstdio>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std;
int head[100], dis[100], book[100], pre[100];
struct note{
int v, w, next;
} edge[100*100];
struct node{
int v, w;
node(int a, int b){
w = a;
v = b;
}
friend bool operator< (node a, node b){
return a.w > b.w;
}
};
int n, m, no;
priority_queue<node> q;
void add(int u, int v, int w){
edge[no].v = v;
edge[no].w = w;
edge[no].next = head[u];
head[u] = no++;
}
void init(){
no = 0;
memset(head, -1, sizeof head);
memset(dis, 0x3f, sizeof dis);
memset(book, 0, sizeof book);
memset(pre, -1, sizeof pre);
}
void Dijkstra(){
while(!q.empty()) q.pop(); //将声明放在bss区(全局区),减少栈内存的消耗,此句清空队列中数据
dis[1] = 0;
q.push(node(0, 1));
while(!q.empty()){
node x = q.top();
q.pop();
if(book[x.v]) continue;
book[x.v] = 1;
int k = head[x.v];
while(k != -1){
if(dis[edge[k].v] > dis[x.v]+edge[k].w){
dis[edge[k].v] = dis[x.v]+edge[k].w;
pre[edge[k].v] = x.v;
q.push(node(dis[edge[k].v], edge[k].v));
}
k = edge[k].next;
}
}
}
void PrintPath(int end)
{
int k = end;
while(k != -1)
{
printf("%d ", k);
k = pre[k];
}
printf("\n");
}
int main(){
int a, b, c, i;
scanf("%d %d", &n, &m);
init();
for(i = 1; i <= m; ++i){
scanf("%d %d %d", &a, &b, &c);
add(a, b, c); //无向图
add(b, a, c);
}
Dijkstra();
for(i = 1; i <= n; ++i)
printf("%d ", dis[i]);
printf("\n");
PrintPath(5); //打印路径
return 0;
}
/*
样例:
5 5
1 2 2
1 3 3
2 4 4
3 4 2
4 5 1
*/
最后一份代码是最终的Dijistra了,如果大家想了解适合稠密图的floyed-Warshell算法,可看上一篇文章,想了解Bellman-ford算法及其队列优化的朋友可看下一篇文章。