深度优先搜索求最短路径
单向路径:
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=99999999;//正无穷
int minn=INF;
int n,e[105][105],book[105];
void dfs(int cur,int dis)
{
int j;
//已经超过前面查找的最短路径,就不需要在查找了
if(dis>minn)
return;
if(cur==n)//到达了
{
minn=min(dis,minn);
return;
}
for(j=1;j<=n;j++)//从1号城市到n号城市依次尝试
{//判断当前城市cur到城市j是否有路,并判断城市j是否已经走过
if(e[cur][j]!=INF&&book[j]==0)
{
book[j]=1;//标记已经走过
dfs(j,dis+e[cur][j]);//从城市j再出发,继续寻找
book[j]=0;
}
}
return;
}
int main()
{
int i,j,m,a,b,c;
scanf("%d%d",&n,&m);
//初始化二维矩阵
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j) e[i][j]=0;//自己
else e[i][j]=INF;
//读入城市之间的距离
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
e[a][b]=c;
}
//从1号城市出发
book[1]=1;
dfs(1,0);
printf("%d",minn);
return 0;
}
/*
5 8//五个城市,10条路径
1 2 2
1 5 10
2 3 3
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3
*/
双向路径:
广度优先搜索求最少转机,书上写的是用数组模拟的,感觉太复杂了,当然这样令问题更直观易懂,有空写个队列的吧。
/*
小哼和小哈一同坐飞机去旅游,他们现在位于1号城市,
目标是5号城市,可是1号城市并没有直接到5号城市的直航.
不过小哼已经收集到了很多航班的信息,现在小哼希望
找到一中乘坐方式,使得转机的次数最少
*/
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int INF = 9999999;
struct node
{
int x;//城市编号
int y;//转机次数
};
int main()
{
struct node que[2505];
int e[55][55]={0},book[55]={0};
int a,b,n,m,cur,start,endd,flag=0;
scanf("%d%d%d%d",&n,&m,&start,&endd);
//初始化二维矩阵
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) e[i][j]=0;
else e[i][j]=INF;
//读入城市之间的航班
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
e[a][b]=1;
e[b][a]=1;
}
//队列初始化
int head=1;
int tail=1;
que[tail].x=start;
que[tail].y=0;
tail++;
book[start]=1;//标记
//当队列不为空的时候循环
while(head<tail)
{
cur=que[head].x;
for(int j=1;j<=n;j++)//尝试n个城市
{
if(e[cur][j]!=INF&&book[j]==0)
{
//符合则入队
que[tail].x=j;
que[tail].y=que[head].y+1;
tail++;
book[j]=1;
}
//已经到达,没必要继续扩展了
if(que[tail-1].x==endd)
{
flag=1;
break;
}
}
if(flag==1)
break;
head++;//扩展
}
//
printf("%d",que[tail-1].y);
return 0;
}
/*
广度优先搜索求最短路径问题解决
Floyd-Warshall算法
通常可以在任何图中使用,包括有向图、带负权边的图,但是不能解决带有“负权回路”(负权环)的图。
Floyd-Warshall 算法用来找出每对点之间的最短距离。
感觉啊哈算法讲的就是详细简单,非常容易理解。
城市+公路(单向道路)
现在需要求任意两个城市之间的最短路程,即两点间的最短路径——“多源最短路径”问题。
仍然用原来的方法,用矩阵存储图,初始化无穷,自己到自己的路程为0。上面写了深度优先搜索和广度优先搜索的方法,时间复杂度是O(n^2),Floyd-Warshall算法是计算每一对顶点间的最短路径,所以时间复杂度是O(n^3).
算法描述:求u和v两点之间的距离,路程的表示可以用直接两点间的边,也可以用中间加入点k1的方法,那么路程就是u到k1的边加上k1到v,然后比较一下取最小值就得到目前状态的最短路径了,然后你还可以通过中间的k1,k2……kn点中转(感觉中转这个词用的特比贴切),每次都取最小值。
例如:求任意两点u和v之间的最短距离时,每次都允许它从1城市中转,那么用数组存储就是e[u][v],中转后的路程就是e[u][1]+e[1][v],两者比较求最小值,再用e[u][v]存储,就是目前状态的最短路径了。
代码实现:
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(e[i][j]>e[i][1]+e[1][j])
e[i][j]=e[i][1]+e[1][j];
}
}
进一步如果允许它从1和2城市中转,那么就是在从1的基础上在从2城市中转,因为此时e[u][v]存储的已经是当前状态的最短路径了,代码实现:
//经过1号顶点
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(e[i][j]>e[i][1]+e[1][j])
e[i][j]=e[i][1]+e[1][j];
}
}
//经过2号顶点
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(e[i][j]>e[i][2]+e[2][j])
e[i][j]=e[i][2]+e[2][j];
}
}
以此类推……,就得到了任意两点间的最短路径(//感觉?:写起来比较简单,但是看起来不方便,就暂时按照啊哈上的写法吧)
//算法实现
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
完整程序:
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int e[10][10],k,i,j,n,m,t1,t2,t3;
int inf=99999999;
cin>>n>>m;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j) e[i][j]=0;
else e[i][j]=inf;
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
}
//Floyd-Warshall算法核心语句
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
printf("%10d",e[i][j]);
}
printf("\n");
}
return 0;
}
注意inf,估计一下最短路径的上限
for(k=1;i<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][k]<inf&&e[k][j]<inf&&e[i][j]>e[i][k]+e[k][j]);
e[i][j]=e[i][k]+e[k][j]
Dijkstra算法——单源最短路
Dijkstra算法是一种求单源最短路的算法,即从一个点开始到所有其他点的最短路。
使用于有向图和无向图,算法描述是从有向图开始的,无向图的每条边可以看成相反的两条边。
不能求带有负权边的图,因为最短距离都是根据上一个确定的状态继续扩展的,如果有负权边,就不能保证扩展这个负权边后的状态了。
算法描述:(还是不太理解专业术语“松弛”)
将所有顶点分为两部分:已知最短路径的集合P和未知最短路径的顶点集合,开始P中只有源点一个顶点,用book[i]来标记在那个集合内,依然用数组来存储点的信息,用dis[i]表示两点间的距离。按路径长度递增次序产生算法,每次新扩展一个距离最短的点,更新与其相邻的点的距离。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = 99999999;
int main()
{
int e[10][10],dis[10],book[10];
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) e[i][j]=0;
else e[i][j]=INF;
int t1,t2,t3;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
e[t1][t2]=t3;
}
//初始化dis数组,哲理是1号顶点到其余各顶点的初始路程
for(int i=1;i<=n;i++)
dis[i]=e[1][i];
memset(book,0,sizeof(book));//初始化
book[1]=1;//因为1点是源点
int minn,u,v;
//Dijkstra算法核心语句
for(int i=1;i<=n-1;i++)//n-1
{
//找到离1号顶点最近的顶点
//是每次都要找
minn=INF;
for(int j=1;j<=n;j++)
{
if(book[j]==0&&dis[j]<minn)
{
minn=dis[j];
u=j;
}
}
book[u]=1;//一个新的起点
for(v=1;v<=n;v++)
{
if(e[u][v]<INF)
{
if(dis[v]>dis[u]+e[u][v])
dis[v]=dis[u]+e[u][v];
}
}
}
//输出
for(int i=1;i<=n;i++)
printf("%d\n",dis[i]);
return 0;
}
/*
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
*/
必须注意:这里的Dijkstra代码是求得的1到任意顶点的最短路径,如果要求s点到任意顶点的最短路径,就要将dis数组的初始化修改为:
for(int i=1;i<=n;i++)
dis[i]=e[s][i];
练习题目见 hdu3790
Bellman-Ford算法——解决负权边
Bellman-Ford算法是求含负权图的单源最短路径算法,算法核心是多轮对所有的边进行松弛。
第一轮在对所有的边进行松弛后,得到的是从1号顶点每次“途径一个边”到达所以顶点的最短路径,要理解这个“途径一个边”的含义,意即:经过一条边,如果可达的顶点,则为其当前状态最短路径;不可达的话,就依然保持其原来的状态∞。
然后接下来进行的第k轮松弛,得到的就是从1号顶点每次“途径k条边”达到所以顶点的最短路径。
那么要进行多少轮松弛,才能真正的得到顶点1到所有顶点的最短路径呢?
理论上来说,要经过n-1轮,即进行完“‘途径n-1条边’达到所有顶点的最短路径”这个状态,这时候已经可以结束了,因为n顶点图中,任意两点间的路径至多是n-1条边(此处不考虑含负权回路的情况)。
//Bellman-Ford算法核心语句
for(int k=1;k<=n-1;k++)// 进行n-1轮松弛
for(int i=1;i<=m;i++)// 枚举每一条边
if(dis[v[i]] > dis[u[i]] + w[i])// 尝试对每一条边进行松弛
dis[v[i]] = dis[u[i]] + w[i];
完整程序:
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int dis[10],n,m,u[10],v[10],w[10];
const int INF = 99999999;
cin>>n>>m;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&u[i],&v[i],&w[i]);
//初始化dis数组,这里的是1号2顶点到其余各顶点的出事路程
for(int i=1;i<=n;i++)
dis[i]=INF;
dis[1]=0;
//Bellman-Ford算法核心语句
for(int k=1;k<=n-1;k++)// 进行n-1轮松弛
for(int i=1;i<=m;i++)// 枚举每一条边
if(dis[v[i]] > dis[u[i]] + w[i])// 尝试对每一条边进行松弛
dis[v[i]] = dis[u[i]] + w[i];
for(int i=1;i<=n;i++)
printf("%d\n",dis[i]);
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
检测负权回路:
用标记变量进行优化:
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int dis[10],n,m,u[10],v[10],w[10],check;
const int INF = 99999999;
cin>>n>>m;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&u[i],&v[i],&w[i]);
//初始化dis数组,这里的是1号2顶点到其余各顶点的出事路程
for(int i=1;i<=n;i++)
dis[i]=INF;
dis[1]=0;
//Bellman-Ford算法核心语句
for(int k=1;k<=n-1;k++)// 进行n-1轮松弛
{
check=0;//标记这一轮是否进行更新
for(int i=1;i<=m;i++)// 枚举每一条边
{
if(dis[v[i]] > dis[u[i]] + w[i])// 尝试对每一条边进行松弛
{
dis[v[i]] = dis[u[i]] + w[i];
check=1;
}
}
if(check==0)//确定dis数组不再进行更新了,就提前退出循环
break;
}
//检测负权回路
int flag=0;
for(int i=1;i<=m;i++)
if(dis[v[i]] > dis[u[i]] + w[i])
flag=1;//还可以继续松弛的话
if(flag)
cout<<"此图含有负权回路"<<endl;
else
for(int i=1;i<=n;i++)
printf("%d\n",dis[i]);
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
队列优化后的bellman——ford算法
http://blog.csdn.net/huatian5/article/details/52550408
现在感觉其实使用STL的基础是你有牢固的基础,STL只是锦上添花,不能过度依赖,不然会忘了其本质,啊哈算法的本质解释非常不错