最短路(常用算法)

模板题目:http://acm.hdu.edu.cn/showproblem.php?pid=2544

n是顶点数,m是边数,参数s是源点,把各个点到源点的最短距离保存在d[maxn]中。

Dijkstra:

思想:

两个集合:未得到最短距离的集合1(初始化1~n)和已得到最短距离集合2(初始化∅) 

vis集合==1属于集合2,否则属于集合1,vis【x】 == 1 说明已经计算出x到源点的最短距离

这就决定了这个算法不能运用于负权值的图上,比如有负权值的环,很明显这个环走的次数越多距离越短,然而这个算法不会去找已经去过的点(集合2)

每次从集合1中找离源点最近的点node1,把node1从集合1删掉加到集合2中,然后以这个点为基础查看集合1的点node2是否可以优化:

如果d[node2] < d[node1] + Map[node1][node2]则更新d[node2] = d[node1] + Map[node1][node2]

直到集合1为∅;

函数里面第一个循环是求目前离源点最短距离的点(第一次进循环后 v == s),只有都求出了最短距离,那么v = -1,结束算法

第二个循环是根据上一个循环算出的最小值试着优化其他节点(加一个判断vis[i]是否为1,就可以只更新集合1的元素,但是不影响时间复杂度所以可以不用写)

 

复杂度O(n^2),只能运用于没有负权值的图(遇到负权值的图就用下面的算法)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 105
#define inf  100000

using namespace std;

int d[maxn];
int s, n, m;
int Map[maxn][maxn];
bool vis[maxn];

void pre()
{
    memset(vis, 0, sizeof(vis));
    for(int i = 0; i < maxn; i++)
    {
        for(int j = 0; j < maxn; j++)
        {
            Map[i][j] = inf;
        }
        d[i] = inf;
    }
}

void  Dijkstra(int s = 1)
{
    d[s] = 0;
    while(true)
    {
        int v = -1;
        for(int i = 1; i <= n; i++)
        {
            if(!vis[i] && (v == -1 || d[i] < d[v]))
            {
                v = i;
            }
        }
        if(v == -1)
            break;

        vis[v] = 1;

        for(int i = 1; i <= n; i++)
        {
            d[i] = min(d[i], d[v] + Map[v][i]);
        }

    }
}


int main()
{
    while(~scanf("%d %d", &n, &m), n && m)
    {
        pre();
        for(int i = 0; i < m; i ++)
        {
            int s, e, cost;
            scanf("%d %d %d", &s, &e, &cost);
            Map[s][e] = Map[e][s] = cost;
        }
        Dijkstra();
        cout << d[n] << endl;
    }
    return 0;
}

用堆优化的Dijkstra:复杂度ElogV

主要优化了找最小距离和松弛操作

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#define maxn 105

using namespace std;

typedef pair<int, int> P;//pair的好处是不用重载小于,可以直接比大小
const int inf = 1000000000;
int n, m;
int head[maxn];
bool vis[maxn];
int d[maxn];
struct edge
{
    int to;
    int cost;
};
vector<edge> M[maxn];

void dijkstra(int s = 1)
{
        for(int i = 1; i <= n; i++)
        {
            d[i] = inf;
            vis[i] = 0;
        }
        d[s] = 0;
        priority_queue<P, vector<P>, greater<P>> PQ;
        PQ.push(make_pair(0, s));
        while(!PQ.empty())
        {
            P cur = PQ.top(); PQ.pop();
            int v = cur.second;
            if(vis[v]) continue;
            vis[v] = 1;
            for(int i = 0; i < M[v].size(); i++)
            {
                edge e = M[v][i];
                int u = e.to;
                int cost = e.cost;
                if(d[u] > d[v] + cost)
                {
                    d[u] =  d[v] + cost;
                    PQ.push(make_pair(d[u], u));
                }
            }
        }
        cout << d[n] << endl;
}

int main()
{
    while(cin >> n >> m, n + m)
    {
        memset(head, -1, sizeof(head));
        for(int i = 0; i < maxn; i++)
            M[i].clear();
        for(int i = 0; i < 2 * m; i += 2)
        {
            int u, v, c;
            scanf("%d%d%d", &u, &v, &c);
            edge tmp;
            tmp.to = v;
            tmp.cost = c;
            M[u].push_back(tmp);
            tmp.to = u;
            tmp.cost = c;
            M[v].push_back(tmp);
        }
        dijkstra();
    }
    return 0;
}

 

或者链式前向星

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>

using namespace std;

const int INF = 1e9 + 7;
const int M = 50005;
int n, m, edgeNum;
long long dis[M];
int head[M];
int visit[M];
int weight[M];

struct Edge
{
    int w;
    int to;
    int next;
} edge[M*2];

struct Node
{
    int u;
    int dis;
    bool operator < (const Node &a) const
    {
        return dis > a.dis;
    }
};

void init()
{
    edgeNum=0;
    for(int i = 0; i < M; i++)
    {
        visit[i] = 0;
        head[i] = -1;
        dis[i] = INF;
    }
}

void addEdge(int a, int b, int c)
{
    edge[edgeNum].w = c;
    edge[edgeNum].to = b;
    edge[edgeNum].next = head[a];
    head[a] = edgeNum++;
}

void dijkstra(int u = 1)
{
    int i, v;
    Node temp,now;
    priority_queue<Node> q;
    temp.dis = 0;
    temp.u = u;
    dis[u] = 0;
    q.push(temp);
    while(!q.empty())
    {
        temp = q.top();
        q.pop();
        visit[temp.u] = 1;
        for(i = head[temp.u]; i != -1; i = edge[i].next)
        {
            v = edge[i].to;
            if(!visit[v] && dis[v] > dis[temp.u] + edge[i].w)
            {
                dis[v] = dis[temp.u] + edge[i].w;
                now.dis = dis[v];
                now.u = v;
                q.push(now);
            }
        }
    }
    cout << dis[n] << endl;
    return ;
}
int main()
{
    while(scanf("%d%d",&n,&m), n + m)
    {
        init();
        for(int i = 0; i < m; i++)
        {
            int u, v, c;
            scanf("%d %d %d", &u, &v, &c);
            addEdge(u, v, c);
            addEdge(v, u, c);
        }
        dijkstra();
    }
    return 0;
}

 

 

 

 

Bellman-Ford:

 

思路:因为有:d[i] = min(d[i], d[j] + cost(j, i))或者写成d[i] = min(d[j] + cost(j, i))  -->> 假设存在j到i的边

我们遍历每条边进行松弛操作(d[i] = min(d[j] + cost(j, i)) ,松弛成功的话,则s到i就经过(j,i)这条边),因为后续更新可能导致现在的d[j]发生变化,所以要一直更新到没有边松弛成功

补充:

什么时候肯定会结束,或者说能不能结束?

因为最短路径肯定是个简单路径,不可能包含回路的,如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径

如果回路的权值是负的,明显无解

图有n个点,又不能有回路,所以最短路径最多n-1边,所以最多松弛n-1次(有解情况下),如果第n次还有边可以松弛说明存在负权值的环,无解

所以在有负权值的图上求最短路时,可以记录while循环了多少次,当第n次update还为true时,说明有负环。

复杂度:O(n * m)

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 105
#define maxm 10005
#define inf  100000

using namespace std;

int n, m;
int d[maxn];

struct edge
{
    int s;
    int e;
    int cost;
}node[maxm];

void pre()
{
        fill(d, d + maxn, inf);
}

void  Bellman_Ford(int s = 1)
{
        d[s] = 0;
        while(true)
        {
            bool update = false;
            for(int i = 0; i < m; i++)
            {
                edge E = node[i];
                if(d[E.s] != inf && d[E.e] > d[E.s] + E.cost)
                {
                    d[E.e] = d[E.s] + E.cost;
                    update = true;
                }
            }
            if(!update)
                break;
        }
}

int main()
{
    while(~scanf("%d %d", &n, &m), n && m)
    {
        pre();
        m <<= 1;
        for(int i = 0; i < m; i += 2)
        {
            int s, e, cost;
            scanf("%d %d %d", &s, &e, &cost);
            node[i].s = s;
            node[i].e = e;
            node[i].cost = cost;
            node[i + 1].e = s;
            node[i + 1].s = e;
            node[i + 1].cost = cost;
        }
        Bellman_Ford();
        cout << d[n] << endl;
    }
    return 0;
}

SPFA(Bellman-Ford的队列实现 ):

 

可以参考这个

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxn 105
#define maxm 10005
#define inf  100000

using namespace std;

int n, m;
int d[maxn];
int Map[maxn][maxn];
bool vis[maxn];

void pre(int s = 1)
{
    fill(d, d + maxn, inf);
    fill(vis, vis + maxn, false);
    for(int i = 0; i < maxn; i++)
    {
        for(int j = 0; j < maxn; j++)
        {
            Map[i][j] = inf;
        }
    }
    d[s] = 0;
}

void SPFA(int s = 1)
{
    queue<int>  q;
    q.push(s);
    vis[s] = 1;
    while(!q.empty())
    {
        int pos = q.front();
        q.pop();
        vis[pos] = false;
        for(int i = 1; i <= n; i++)
        {
            if(d[pos] + Map[pos][i] < d[i])
            {
                d[i] = d[pos] + Map[pos][i];
                if(!vis[i])
                {
                    q.push(i);
                    vis[i] = true;
                }
            }
        }
    }
}

int main()
{
    while(~scanf("%d %d", &n, &m), n && m)
    {
        pre();
        for(int i = 0; i < m; i++)
        {
            int s, e, cost;
            scanf("%d %d %d", &s, &e, &cost);
            Map[s][e] = Map[e][s] = cost;
        }
        SPFA();
        cout << d[n] << endl;
    }
    return 0;
}

Floyd-Wallshall

 

求解所有两点间的最短路

有dp状态转移方程 d[k][i][j] = min(d[k - 1][i][k] + d[k - 1][k][j]),

其中k表示用1-k个点和i,j两点的情况下i到j的最短距离

用二维数组就行

O(n^3),稳定,可以处理负权边(检查是否存在d[i][i]为负数,因为如果没有负圈的话d[i][i]明显等于0)。

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxn 105
#define maxm 10005
#define inf  100000

using namespace std;

int n, m;
int d[maxn][maxn];

void pre(int s = 1)
{
    for(int i = 0; i < maxn; i++)
    {
        for(int j = 0; j < maxn; j++)
        {
            d[i][j] = inf;
        }
        d[i][i] = 0;
    }
}

void Floyd_Wallshall(int s = 1)
{
    for(int k = 1; k <= n; k++)
    {
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
}

int main()
{
    while(~scanf("%d %d", &n, &m), n && m)
    {
        pre();
        for(int i = 0; i < m; i++)
        {
            int s, e, cost;
            scanf("%d %d %d", &s, &e, &cost);
            d[s][e] = d[e][s] = cost;
        }
       Floyd_Wallshall();
        cout << d[1][n] << endl;
    }
    return 0;
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值