单源最短路径
给定一个点,寻找它到每个点权值都最小的边
Dijkstra
伪代码描述
变量描述:给定一个顶点s,d[i]为s->i的最短路径,p[i]存下i的上一个顶点,visit[i]用来标记节点(0未标记,1标记)
以下为了循环好写一点,令s=0,共有n个顶点;
1.初始化:d[0]=0,for(int i=0;i<n;i++)d[i]=INF;
2.for i=1:n-1
- 在未标定的顶点中,找到d[i]最短的顶点u。
- 标记u,visit[u]=1
- 更新d[i],如果d[i]>d[u]+w[u][i],d[i]=d[u]+w[u][i], 且p[i]=u;
end
//Dijkstra算法 单元最短路径
void Dijkstra(AdjGraph& G,int s) {
int n = G.VertexNum();
int *visit = new int[n];
int *d = new int[n];
int *p = new int[n];
for (int i = 0; i < n; i++) {//初始化
visit[i] = 0;
d[i] = INFINITY;
if (i == s) {
d[s] = 0;
p[s] = s;
}
}
int control = 0;//n次循环
int flag = true;
while (control<n) {
int min = INFINITY;
int u = 0;
//找到未标定的点u且d[u]最小
for (int i = 0; i < n; i++) {
if (!visit[i] && d[i] <= min) {
min = d[i];
u = i;
}
}
if (flag) {//第一次先标定起始点s
u = s;
flag = false;
}
visit[u] = 1;//标记访问过u
//更新d[i]
for (Edge e = G.FirstEdge(u); G.isEdge(e); e = G.NextEdge(e)) {
if (d[e.end] > d[u] + e.weight) {
d[e.end] = d[u] + e.weight;
p[e.end] = u;
}
}
control++;
}
//输出
cout << "start point:" << s << endl;
cout << "path:" << endl;
for (int i = 0; i < n; i++) {
cout << "end point:" << i << " path:"<<i;
int tmp = p[i];
while (tmp != s) {
cout << "<--" << tmp;
tmp = p[tmp];
}
if(i!=s) cout << "<--" << s;
cout << " weight:"<<d[i]<<endl;
}
}
不难看出,上面程序的时间复杂度为O(n^2)
优化到O(mlogn):
1.用邻接表存储图
2.用优先队列(stl中的priority_queue)
- 为什么是O(mlogn)咧?
优先队列的push,pop操作复杂度是logn的(因为优先队列的数据结构是二叉堆,即完全二叉树,搜索复杂度为logn)
代码来自刘汝佳的《算法竞赛入门》
#include<iostream>
#include <functional>
#include<queue>
#define MAXM 100
using namespace std;
int f[MAXM];//保存下标(节点编号)的第一条边 的编号
int u[MAXM], v[MAXM], w[MAXM], Next[MAXM];//u[e],v[e]存的是边e(下标)的起始和结束节点编号;
//Next[e]表示编号为e的边的下一条边编号
int n, m;
typedef pair<int, int> pp;//pp代表一对数字:d[i]和i
void read_graph()
{
cin >> n >> m;;
for (int i = 0; i < n; i++)f[i] = -1;
for (int e = 0; e < m; e++) {//e表示边的下标
cin >> u[e] >> v[e] >> w[e];
Next[e] = f[u[e]];//f[i],i代表的总是顶点下标,所以一直是u[e]
f[u[e]] = e;
}
}
void dijkstra() {
int *d = new int[n];
int *p = new int[n];
int *done = new int[n];
p[0] = 0;
priority_queue<pp,vector<pp>,greater<pp> > q;//最小堆,greater<pp> 表示cmp大于,头文件functional
//pp是一个二元组,pair比较大小时先比较 第一维
for (int i = 0; i < n; i++) {
d[i] = (i == 0 ? 0 : MAXM);
done[i] = 0;//节点全都未访问过
p[i] = -1;
}
q.push(make_pair(d[0],0));//make_pair->pp类型
while (!q.empty()) {
pp m = q.top();//取出d[u]最小的顶点u
q.pop();
int x = m.second;//顶点 m.first是权重,m.second是顶点编号
if (done[x])continue;//被访问过就跳过
done[x] = 1;//访问后标记一下
for (int e = f[x]; e != -1; e = Next[e]) {
if (d[v[e]] > d[x] + w[e]) {
p[v[e]] = x;//x->v[e]
d[v[e]] = d[x] + w[e];
q.push(make_pair(d[v[e]], v[e]));
}
}
}
cout << "start point:" << 0 << endl;
cout << "path:" << endl;
for (int i = 1; i < n; i++) {
cout << "end point:" << i << " path:" << i;
int tmp = p[i];//i的前驱节点
while (tmp != -1) {//不是0
cout << "<--" << tmp;
tmp = p[tmp];//tmp的前驱节点
}
cout << " weight:" << d[i] << endl;
}
}
int main()
{
read_graph();
dijkstra();
return 0;
}
Bellman-Ford算法
Dijkstra无法处理无项负权边的情况
BF算法可以解决 单元最短路径同时也能够处理负边的出现。
即能够判断是否有从源点可达的负环。
算法描述(bool bf(int s):返回false就是存在负边环;返回true即d数组就是单源最短路径):
(用邻接表存储图)
while循环下列操作n-1次:
1.遍历每条边e:u->v
2.如果d[v] > d[u] + w(e), 就更新d[v]
end
然后再遍历一遍边,执行1,2操作,如果有d[v]<d[u]+w(e),说明存在负边环,返回false;
SPFA(shortest path Faster algorithm)
基于Bellman-Ford的优化
其实不用遍历所有的边,只需要遍历d[i]被修改的点的边就行了。
于是用队列来进行操作。
每次取队列队首元素v,然后遍历这个v的边 ,如果d[i]改变,i入队列
直到队列为空
(如果有负环还需增添数组来计算节点入队列的次数如果大于n-1就存在源点可达的负环。)
Floyd算法—解决全源最短路径问题
[这个描述挺清楚地](http://wiki.jikexueyuan.com/project/easy-learn-algorithm/floyd.html)
核心代码部分
for(k=0;k<n;k++)
{
for(i=0;i<n;i++)
for(j=0;j<n;j++)
if(A[i][j]>(A[i][k]+A[k][j]))
{
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;
}
}
算法流程描述:
选择一个节点k,然后遍历图(二维数组),如果A[i][j] > A[i][k]+A[k][j];
就更新A[i][j]=A[i][k]+A[k][j];
直到k取完图中节点~
算法复杂度为O(n^3).
习题
/*1003.Emergency*/
#include<iostream>
#include<climits>
using namespace std;
int n;//几个城市
int *num;//一个城市有几个救援队
int **G;//城市图
//int *d,*visit,*pre;//单源最短路径,是否访问过,前驱顶点,这里和Dijkstra是一样的
//int *road, *maxTeam;//road[i]代表起点到i有几条最短路径,maxTeam[i]为s->i最大救援队数量
//initiate: road[s]=1;road[i]=0;maxTeam[s]=num[s].maxTeam[i]=0;
void Dijkstra(int start, int end) {
//给d,visit,pre,road,maxTeam开辟空间并且初始化
int *d = new int[n];
int *visit = new int[n];
//int *pre = new int[n];
int *road = new int[n];
int *maxTeam = new int[n];
for (int i = 0; i < n; i++) {
d[i] = INT_MAX;
visit[i] = 0;
//pre[i] = -1;
road[i] = 0;
maxTeam[i] = 0;
}
d[start] = 0;
//pre[start] = start;
road[start] = 1;
maxTeam[start] = num[start];
if (start == end) {
cout << road[end] << " " << maxTeam[end] << endl;
return;
}
int control = 0;
bool flag = true;
while (control < n) {
int u = 0;
int min = INT_MAX;
//找到未访问过且d[u]最小的顶点,第一次从s开始,即u=s
for (int i = 0; i < n; i++) {
if (!visit[i] && d[i] < min) {
min = d[i];
u = i;
}
}
visit[u] = 1;//标记访问过
for (int j = 0; j < n; j++) {//从u出发
if (G[u][j] != 0 && !visit[j]) {//有路
if ( d[j] > d[u] + G[u][j]) {
d[j] = d[u] + G[u][j];
//pre[j] = u;
maxTeam[j] = maxTeam[u] + num[j];
road[j] = road[u];
}
else if (d[j] == d[u] + G[u][j]) {
road[j] = road[j] + road[u];
if (maxTeam[j] < maxTeam[u] + num[j]) {
maxTeam[j] = maxTeam[u] + num[j];
}
}
}
}
control++;
}
cout << road[end] << " " << maxTeam[end] << endl;
}
int main()
{
int m,s, e;
cin >> n >> m >> s >> e;
num = new int[n];
G = (int **)new int*[n];
for (int i = 0; i < n; i++) {//初始化图G,有n个城市(顶点)
G[i] = new int[n];
for (int j = 0; j < n; j++) {
G[i][j] = 0;//表示没路
}
}
for (int i = 0; i < n; i++) {
cin >> num[i];//一个城市有几个救援队
}
int x, y, w;
for (int i = 0; i < m; i++) {//初始化路径
cin >> x >> y >> w;
G[x][y] = w;
}
Dijkstra(s,e);
return 0;
}
/*1030 Travel Plan*/
#include<iostream>
#include<climits>
#include<vector>
#include<iterator>
using namespace std;
const int N = 510;
int n,m, s, e;;//几个城市
int **G;//城市图
int Cost[N][N] = { 0 };
vector<int> pre[N];//节点i的前驱节点数组
vector<int> Path, tmpPath;
int *d = new int[N];
int Mincost = INT_MAX;
void Dijkstra() {
int *visit = new int[n];
for (int i = 0; i < n; i++) {
d[i] = INT_MAX;
visit[i] = 0;
}
d[s] = 0;
int control = 0;
while (control < n) {
int u = -1;
int min = INT_MAX;
//找到未访问过且d[u]最小的顶点
for (int i = 0; i < n; i++) {
if (!visit[i] && d[i] < min) {
min = d[i];
u = i;
}
}
if(u == -1)return;
visit[u] = 1;//标记访问过
for (int j = 0; j < n; j++) {//从u出发
if (G[u][j] != 0 && !visit[j]) {//有路
if ( d[j] > d[u] + G[u][j]) {
d[j] = d[u] + G[u][j];
pre[j].clear();
pre[j].push_back(u);
}
else if (d[j] == d[u] + G[u][j]) {
pre[j].push_back(u);
}
}
}
control++;
}
}
void DFS(int v) {//DFS输出Cost最小的路线
if (v == s) {//到达边界
tmpPath.push_back(v);
//计算cost
int cost = 0;
for (int i = 0; i < tmpPath.size()-1; i++) {
int u = tmpPath[i];
int v = tmpPath[i + 1];
cost += Cost[u][v];
}
if (cost < Mincost) {
Mincost = cost;
Path.clear();
vector<int>::iterator it;
for (it = tmpPath.begin(); it != tmpPath.end(); it++)
Path.push_back(*it);
}
tmpPath.pop_back();
return;
}
//递归
tmpPath.push_back(v);
for (int j = 0; j < pre[v].size(); j++) {
DFS(pre[v][j]);
}
tmpPath.pop_back();
}
int main()
{
cin >> n >> m >> s >> e;
G = (int **)new int*[n];
for (int i = 0; i < n; i++) {//初始化图G,有n个城市(顶点)
G[i] = new int[n];
for (int j = 0; j < n; j++) {
G[i][j] = 0;//表示没路
}
}
int x, y, w,c;
for (int i = 0; i < m; i++) {//初始化路径和消耗权重
cin >> x >> y >> w>>c;
G[x][y] = w;
G[y][x] = w;
Cost[x][y] = c;
Cost[y][x] = c;
}
Dijkstra();
DFS(e);
for (int i = Path.size() - 1; i >= 0; i--) {
cout << Path[i] << " ";
}
cout << d[e] << " " << Mincost;
return 0;
}