POJ 3259--Wormholes(求负环)

32 篇文章 0 订阅
24 篇文章 0 订阅

题目要求有向图G(V,E)是否存在负环,求负环的方法有Bellman-Ford以及SPFA。

Bellman-Ford:复杂度O(V*E),加入优化可0ms。

按照算法导论中的讲解,简单地理解就是若不存在负环,则源节点到各个节点的最短路径必定存在。假设v0,vk分别为始终点,路径为v0,v1,...,vk,最短路径最多只能有|V|-1条边组成。我们每次对全部边作松弛操作,最多进行|V|-1次上述操作,则我们进行的松弛操作的顺序必定包含有(v0,v1),(v1,v2),...,(vk-1,vk),根据归纳法可知经过上述顺序的松弛必定能找到v0到vk的最短路径。

若图中存在负环,显然从源点到环中的点的距离是可以一直减小至无穷小的,则我们可以一直松弛下去,即再进行第|V|次对全部边的松弛操作,会有点的距离减小则存在负环。

非连通图的负环判别:算法导论中只说Bellman-Ford能找出从源点出发的负环,但对于此题,负环可能不跟1节点连通,则我们不是要进行V*V*E的操作?当然是不必要的。

方法1:注意到算法导论中源点到其它点的距离初始化为无限大,所以无限大减去一个数仍然是无限大,所以与源点非连通的点永远不能被松弛。但是我们不能赋初值为无限大(直接判别除外),只能赋一个整型的大值,而且也不需要赋为无限大。只要存在负环,若产生松弛,则肯定能松弛下去,则我们赋各个距离初值为一个较大的值,当然这个值不能影响求最短距离,但是又要使跟源点非连通的点能产生松弛,注意到第一次这种松弛肯定产生于负权边上。

方法2:新生成一个节点,使它到所有节点距离为0,则从负权边出发可以对全部图进行松弛。也可以理解为以各个负权边起始点作为源点对图中个点进行松弛。

Bellman-Ford优化1:主要针对不存在负环的图,每当对全部边作松弛之前设置一个变量IsRelax为FALSE,代表没有松弛操作发生,若发生松弛则置其为true。当我们还未进行|V|-1次循环,但是已经没有点的距离减小的话,则我们再循环也不会有松弛发生,也不存在负环。

优化2:利用上述不连通图中方法2,并用并查集检查环路,注意到祖先肯定是负权边起始点,则起始点距离(初始化0)经过环路变小说明存在负环。


SPFA:类似于BFS,是Bellman-Ford的优化。当有点松弛时,且此点未在队列中,则我们把此点放入队列中再去松弛其他点,设pos[i]为i点在路径中位置,则经过i点松弛到的点的位置是pos[i]+1,当pos[x] == |V|时,说明存在负环。类似Bellman-Ford判无连通图的负环可以先把所有点加入队列中,并且加入并查集优化。

SPFA查负环性能比Bellman-Ford略差。


Bellman-Ford:

#include<cstdio>
#include<cstring>
#define maxN 502
#define maxM 5202
#define INF  0X7F7F

int fieldNum,edgeNum;
short g[maxN][maxN];
struct edge
{
    short px,py;
}edges[maxM];

int Bellman_Ford(int nodeNum)
{
    int i,j;
    short x,y;
    int dis[maxN];
    short fa[maxN];
    char IsRelax;
    for(i = 1;i <= nodeNum;i++)
        fa[i] = i;
    memset(dis,0,sizeof(dis));
    for(i = 1;i < nodeNum;i++)
    {
        IsRelax = false;
        for(j = 0;j < edgeNum;j++)
        {
            x = edges[j].px;
            y = edges[j].py;
            if(dis[y] > dis[x]+g[x][y])
            {
                if(fa[x] == y)       //有环且初始点距离被更新则说明存在负环
                    return true;
                IsRelax = true;
                fa[y] = fa[x];
                dis[y] = dis[x]+g[x][y];
            }
        }
        if(!IsRelax)        //已经没有点被松弛,则再循环也不会松弛且说明没有负环
            return false;
    }
    for(i = 0;i < edgeNum;i++)
    {
        x = edges[i].px;
        y = edges[i].py;
        if(dis[y] > dis[x]+g[x][y])     //负环能无限松弛
            return true;
    }
    return false;
}

int generateGraph(char* s,bool edgeFlag)
{
    int a[2];
    int i = 0;
    int num = 0;
    while(*s)
    {
        if(*s != ' ')
        {
            num *= 10;
            num += *s - '0';
        }
        else
        {
            a[i++] = num;
            num = 0;
        }
        s++;
    }
    if(edgeFlag)
    {
        if(g[a[0]][a[1]] > num)
            g[a[0]][a[1]] = g[a[1]][a[0]] = num;
    }
    else if(g[a[0]][a[1]] > -1*num)
            g[a[0]][a[1]]= -1*num;
    return 0;
}

int generateEdge()
{
    int i,j;
    edgeNum = 0;
    for(i = 1;i <= fieldNum;i++)
    for(j = 1;j <= fieldNum;j++)
    {
        if(g[i][j] !=  INF)
        {
            edges[edgeNum].px = i;
            edges[edgeNum++].py = j;
        }
    }
    return 0;
}

int main()
{
    int i;
    int farmNum,pathNum,holeNum;
    char strIn[15];
    scanf("%d",&farmNum);
    while(farmNum--)
    {
        scanf("%d%d%d",&fieldNum,&pathNum,&holeNum);
        memset(g,0X7F,sizeof(g));
        getchar();
        for(i = 0;i < pathNum+holeNum;i++)
        {
            gets(strIn);
            if(i < pathNum)
                generateGraph(strIn,1);    //生成有向图
            else
                generateGraph(strIn,0);
        }
        generateEdge();    //提取出图中的边
        if(Bellman_Ford(fieldNum))
             printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}


SPFA:

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define maxN 502
#define INF  0X7F7F

int SPFA(int nodeNum,short (*g)[maxN])
{
    short i;
    short now;
    char IsInQ[maxN];
    short fa[maxN];
    short updateNum[maxN];
    int dis[maxN];
    for(i = 1;i <= nodeNum;i++)
        fa[i] = i;
    memset(dis,0,sizeof(dis));
    memset(updateNum,0,sizeof(updateNum));
    queue<short> q;
    for(i = 1;i <= nodeNum;i++)
    {
        q.push(i);
        IsInQ[i] = true;
    }
    while(!q.empty())
    {
        now = q.front();
        q.pop();
        IsInQ[now] = false;
        for(i = 1;i <= nodeNum;i++)
        {
            if(g[now][i] != INF&&dis[now]+g[now][i] < dis[i])
            {
            if(fa[now] == i)
                return true;
            fa[i] = fa[now];
            dis[i] = dis[now]+g[now][i];
            if(!IsInQ[i])
            {
                updateNum[i] = updateNum[now]+1;
                if(updateNum[i] == nodeNum)
                    return true;
                IsInQ[i] = true;
                q.push(i);
            }
            }
        }
    }
    return false;
}

int generateEdge(char* s,short (*g)[maxN],bool edgeFlag)
{
    int a[2];
    int i = 0;
    short num = 0;
    while(*s)
    {
        if(*s != ' ')
        {
            num *= 10;
            num += *s - '0';
        }
        else
        {
            a[i++] = num;
            num = 0;
        }
        s++;
    }
    if(edgeFlag)
    {
        if(g[a[0]][a[1]] > num)
            g[a[0]][a[1]] = g[a[1]][a[0]] = num;
    }
    else if(g[a[0]][a[1]] > -1*num)
            g[a[0]][a[1]]= -1*num;
    return 0;
}


int main()
{
    int i;
    short g[maxN][maxN];
    int farmNum,fieldNum,pathNum,holeNum;
    char strIn[15];
    scanf("%d",&farmNum);
    while(farmNum--)
    {
        memset(g,0X7F,sizeof(g));
        scanf("%d%d%d",&fieldNum,&pathNum,&holeNum);
        getchar();
        for(i = 0;i < pathNum+holeNum;i++)
        {
            gets(strIn);
            if(i < pathNum)
                generateEdge(strIn,g,1);
            else
            generateEdge(strIn,g,0);
        }
        if(SPFA(fieldNum,g))
             printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值