最短路径 啊哈算法读书笔记

深度优先搜索求最短路径

单向路径:

#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;
}
/*

广度优先搜索求最短路径问题解决

HDOJ1548解析       HDOJ 2544解析


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]

HDOJ 2544解析

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
*/

HDOJ 2544解析

必须注意:这里的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只是锦上添花,不能过度依赖,不然会忘了其本质,啊哈算法的本质解释非常不错


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值