图论(1)

Floyd 算法

//java 和 c++
for(int k=1; k<=n; k++)         //floyd的三重循环
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)      // k循环在i、j循环外面
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

Dijkstra 算法

  • Dijkstra:单源最短路径问题。
  • 优点:非常高效而且稳定。
  • 缺点:只能处理不含有负权边的图。
  • 思路:贪心思想+优先队列。
  • void dijkstra(){
        int s = 1;             //起点s是1
        bool done[N]; //done[i]=true表示到结点i的最短路径已经找到
        for (int i=1;i<=n;i++) {dis[i]=INF; done[i]=false; }    //初始化
        dis[s]=0;                           //起点到自己的距离是0
        priority_queue <s_node> Q;          //优先队列,存结点信息
        Q.push(s_node(s, dis[s]));          //起点进队列
        while (!Q.empty())   {
            s_node u = Q.top();             //pop出距起点s距离最小的结点u
            Q.pop();
            if(done[u.id])  continue;       //丢弃已经找到最短路径的结点。即集合A中的结点            
            done[u.id]= true;
            for (int i=0; i<e[u.id].size(); i++) {  //检查结点u的所有邻居
                edge y = e[u.id][i];         //u.id的第i个邻居是y.to
                if(done[y.to])  continue;    //丢弃已经找到最短路径的邻居结点                
                if (dis[y.to] > y.w + u.n_dis) {
                    dis[y.to] = y.w + u.n_dis;
                    Q.push(s_node(y.to, dis[y.to]));  //扩展新的邻居,放到优先队列中
                    pre[y.to]=u.id;  //如果有需要,记录路径
                }
            }
        }
        // print_path(s,n);          //如果有需要,打印路径: 起点1,终点n
    }

    Bellman-Ford 算法

    BFS 的扩散思想,每个人都去问自己的相邻节点到 S 点的距离最近是多少。

    第一轮至少有一个点得到了到 S 的最短距离,即与 S 相邻的节点,标记为 T1

    重复以上操作,那么必然至少又有一个节点找到了与 S 的最短距离,即与 T1 相邻的节点,标记为 T2

    一共需要几轮操作?

    每一轮操作,都至少有一个新的结点得到了到 S 的最短路径。所以,最多只需要 n 轮操作,就能完成 n 个结点。在每一轮操作中,需要检查所有 m 个边,更新最短距离。

    Bellman-Ford 算法的复杂度:O(nm)

    Bellman-Ford 能判断负圈:

    没有负圈时,只需要 n 轮就结束。

    如果超过 n 轮,最短路径还有变化,那么肯定有负圈。

    SPFA 算法

    队列优化版的 Bellman-Ford

    SPFA = 队列处理+Bellman-Ford。

    Bellman-Ford 算法有很多低效或无效的操作。其核心内容,是在每一轮操作中,更新所有结点到起点 S 的最短距离。 计算和调整一个结点 U 到 S 的最短距离后,如果紧接着调整 U 的邻居结点,这些邻居肯定有新的计算结果;而如果漫无目的地计算不与 U 相邻的结点,很可能毫无变化,所以这些操作是低效的。

    改进: 计算结点 U 之后,下一步只计算和调整它的邻居,能加快收敛的过程。 这些步骤用队列进行操作,这就是 SPFA。

    (1)起点 S 入队,计算它所有邻居到 S 的最短距离。把 S 出队,状态有更新的邻居入队,没更新的不入队。 (2)现在队列的头部是 S 的一个邻居 U。弹出 U,更新它所有邻居的状态,把其中有状态变化的邻居入队列。 (3)继续以上过程,直到队列空。这也意味着,所有结点的状态都不再更新。最后的状态就是到起点 S 的最短路径。

    弹出 U 之后,在后面的计算中,U 可能会再次更新状态(后来发现,U 借道别的结点去 S,路更近)。所以,U 可能需要重新入队列。 有可能只有很少结点重新进入队列,也有可能很多。这取决于图的特征。

    所以,SPFA 是不稳定的,所以根据题目的类型,我们要选择合适的算法。

    SPFA 模板如下:

    #include<bits/stdc++.h>
    using namespace std;
    const long long INF = 0x3f3f3f3f3f3f3f3f;
    const int N = 5e3+10;
    struct edge{
        int to;    long long w;
        edge(int tt,long long ww) {to = tt; w = ww;}
    };
    long long dist[N];
    int inq[N];
    vector<edge> e[N];
    void spfa(int s){
        memset(dist,0x3f,sizeof(dist));
        dist[s] = 0;      //起点到自己的距离是0
        queue<int> q;
        q.push(s);        //从s开始,s进队列
        inq[s] = 1;       //起点在队列中
        while(!q.empty()) {
            int u = q.front();
            q.pop();
            inq[u] = 0;   //u已经不在队列中
            if(dist[u] == INF)     continue;
            for(int i = 0;i < e[u].size();i++) {   //遍历u的邻居
                int v = e[u][i].to;
                long long w = e[u][i].w;
                if(dist[v] > dist[u]+w) {         //u的第i个邻居v,它借道u,到s更近
                    dist[v] = dist[u]+w;          //更新邻居v到s的距离
                    if(!inq[v]) {      //邻居v更新状态了,但v不在队列中,放进队列
                        q.push(v);
                        inq[v] = 1;
                    }
                }
            }
        }
    }
    int main(){
        int n,m,s;cin>>n>>m>>s;
        for(int i = 1;i <= m;i++)    {
            int u,v; long long w;
            cin>>u>>v>>w;
            e[u].push_back(edge(v,w));
        }
        spfa(s);
        for(int i = 1;i <= n;i++) {
            if(dist[i]==INF)  cout << -1;
            else              cout << dist[i];
            if(i != n)        cout << " ";
            else              cout << endl;
        }
        return 0;
    }

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值