Bellman_Fofd+SPFA+判负环

Bellman-Ford

最短路Dijkstra不适用于带负权的图。

为什么Dijkstra 算法不适用于带负权的图?
当把一个点选入集合S时,就意味着已经找到了从A到这个点的最短路径,比如第二步,把C点选入集合S,这时已经找到A到C的最短路径了,但是如果图中存在负权边,就不能再这样说了。
在这里插入图片描述
在这个图中,求从A到C的最短路,如果用Dijkstra根据贪心的思想,选择与A最接近的点C,长度为7,以后不再变化。但是很明显此图最短路为5。归结原因是Dijkstra采用贪心思想,不从整体考虑结果,只从当前情况考虑选择最优。

为了能够求解含负权边的带权有向图的单源最短路径问题,Bellman(贝尔曼)和Ford(福特)提出了从源点逐次绕过其他顶点,以缩短到达终点的最短路径长度的方法。
枚举所有的点,能松弛就进行松弛操作,直到所有点都不能松弛了

Bellman-Ford算法的限制条件:要求图中不能包含权值总和为负值回路(负权值回路),如下图所示。
在这里插入图片描述
初始化:将除源点外的所有顶点的最短距离估计值 :
d[v] ←+∞, d[s] ←0;


const int INF=0x3f3f3f3f;
const int maxn=1010;
struct edge{   
   int u,v,cost;
}map[maxn];
int dis[maxn],pre[maxn];  //dist存最短路的值,pre主要用来打印最短路径
int n,m,start;  //n为节点数,m为边的总数,start为起点

vois Bellman_Fofd();{
    for(int i=1;i<=n;i++){
       dis[i]=INF;
    }
    dis[start]=0;
    for(int i=1;i<=n-1;i++)
       for(int j=1;j<=m;j++)
           if(dis[map[j].v]>dis[map[j].u]+map[j].cost){
					dis[map[j].v]=dis[map[j].u]+map[j].cost;
					pre[map[j].v]=map[j].u;
            }
}

bool judge(){  //判断是否存在负环,判断条件为边集E中一条边的任意两个端点是否收敛,如果存在未收敛的顶点,就证明存在负环
     bool flag=1;  //判断是否含有负权回路
     for(int i=1;i<=m;i++){
        if(dis[map[i].v]>dist[map[i].u]+map[i].cost){
           flag=0;
            break;
         }
     }
     return flag;
]

int main(){
   cin>>n>>m>>start;
   int u,v,cost;
   pre[start]start;
   for(int i=1;i<=m;i++){  //建图
     cin>>u>>v>>cost;
     map[i].u=u;
     map[i].v=v;
     map[i].cost=cost;
}
Bellman_Fofd();

SPFA

Bellman-ford的队列优化——SPFA

SPFA 算法的基本思路与 Bellman-Ford 算法相同,即每个节
点都被用作松弛其相邻节点的备选节点。相较于 Bellman-Ford
算法, SPFA 算法的提升在于它并不盲目尝试所有节点,而是维
护一个备选节点队列,并且仅有节点被松弛后才会放入队列中。
整个流程不断重复直至没有节点可以被松弛。

每一次松弛的时候bellmanford都要枚举所有的点,而其实有很多点都是不需要枚举的,所以很多的无效枚举,效率显得略低
其实每次松弛的时候只需要枚举与上次被松弛的点相连的点就可以了
设Dis代表S到I点的当前最短距离,Fa代表S到I的当前最短路径中I点之前的一个点的编号。开始时Dis全部为+∞,只有Dis[S]=0,Fa全部为0。
维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点S。用一个布尔数组记录每个点是否处在队列中。
每次迭代,取出队头的点v,依次枚举从v出发的边v->u,设边的长度为len,判断Dist[v]+len是否小于Dist[u],若小于则改进Dist[u],将Fa[u]记为v,并且由于S到u的最短距离变小了,有可能u可以改进其它的点,所以若u不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是S到所有节点的最短距离都确定下来,结束算法。

若一个点入队次数超过n,则有负权环。

SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。

在Bellman-Ford算法中,要是某个点的最短路径估计值更新了,那么我们必须对所有边的终点再做一次松弛操作;在SPFA算法中,某个点的最短路径估计值更新,只有以该点为起点的边指向的终点需要再做一次松弛操作。在极端情况下,后者的效率将是前者的n倍。

#include<bits/stdc++.h>
using namespace std;
const int emaxn=3000;
const int maxn=500+5;
const int INF=0x3f3f3f3f;
struct edge{
   int u,v,w,next;
}edge[emaxn];

int head[maxn];   //链式前向星存图

int dist[maxn];
int num[maxn];  //入队次数
bool vis[maxn];   //标记数组
int pre[maxn];
int cnt,n;
void init(){
   memset(head,-1,sizeof(head));
   cnt=0;
}

void addedge(int u,int v,int w){
  edge[cnt].u=u;
  edge[cnt].v=v;
  edge[cnt].w=w;
  edge[cnt].next=head[u];
  head[u]=cnt++;
}

bool SPFA(int start){
   memset(dist,INF,sizeof(dist));
   memset(num,0,sizeof(num));
   memset(vis,false,sizeof(vis));
   memset(pre,-1,sizeof(pre));
   dist[start]=0;
   vis[start]=true;
   num[start]=1;  
   queue<int> q;
   q.push(start);
   while(!q.empty()){
      int u=q.front();
      q.pop();
      vis[u]=false;
      for(int i=head[u];~i;i=edge[i].next){
          int v=edge[i].v;
          if(dist[v]>dist[u]+edge[i].w){
              dist[v]=dist[u]+edge[i].w;
          //    pre[v]=u;
              if(!vis[v]){
                vis[v]=true;
                q.push(v);
                if(++num[v]>n) return flase;//存在负环
              }
          }
      }
   }
   return true;
}

int main(){
       int m;
      cin>>n>>m;
       init();
       int s,e,t;
       for(int i=1;i<=m;i++){
          cin>>s>>e>>t;
          addedge(s,e,t);
       }
     
    //   for(int i=1;i<=n;i++){
    //      for(int j=head[i];~j;j=edge[j].next)
      /*         printf("%d-> %d \n",edge[j].u,edge[j].v);  */
  //     }
       bool flag;
       flag=SPFA(1);
       if(flag==true)
         printf("YES\n");
       else
        printf("NO\n");
   }
}

问题 A: Arbitrage
题目描述
Arbitrage is the use of discrepancies in currency exchange rates to transform one unit of a currency into more than one unit of the same currency. For example suppose that 1 US Dollar buys 0.5 British pound 1 British pound buys 10.0 French francs and 1 French franc buys 0.21 US dollar. Then by converting currencies a clever trader can start with 1 US dollar and buy 0.5 * 10.0 * 0.21 = 1.05 US dollars making a profit of 5 percent.
Your job is to write a program that takes a list of currency exchange rates as input and then determines whether arbitrage is possible or not.
输入
The input will contain one or more test cases. Om the first line of each test case there is an integer n (1<=n<=30) representing the number of different currencies. The next n lines each contain the name of one currency. Within a name no spaces will appear. The next line contains one integer m representing the length of the table to follow. The last m lines each contain the name ci of a source currency a real number rij which represents the exchange rate from ci to cj and a name cj of the destination currency. Exchanges which do not appear in the table are impossible.
Test cases are separated from each other by a blank line. Input is terminated by a value of zero (0) for n.
输出
For each test case print one line telling whether arbitrage is possible or not in the format “Case case: Yes” respectively “Case case: No”.
样例输入
3
USDollar
BritishPound
FrenchFranc
3
USDollar 0.5 BritishPound
BritishPound 10.0 FrenchFranc
FrenchFranc 0.21 USDollar

3
USDollar
BritishPound
FrenchFranc
6
USDollar 0.5 BritishPound
USDollar 4.9 FrenchFranc
BritishPound 10.0 FrenchFranc
BritishPound 1.99 USDollar
FrenchFranc 0.09 BritishPound
FrenchFranc 0.19 USDollar

0
样例输出
Case 1: Yes
Case 2: No

题意:给出一些货币和货币之间的兑换比率,问是否可以使某种货币经过一些列兑换之后,货币值增加。举例说就是1美元经过一些兑换之后,超过1美元。可以输出Yes,否则输出No。

最短路改进  松弛操作根据题意做了改进


#include<bits/stdc++.h>
using namespace std;
map<string,int> node;
const int maxn=1000;
const int INF=0;
struct edge{
   int u,v,next;
   double w;
}edge[maxn];
int head[30],num[maxn];
bool vis[30];
int pre[30];
double dist[30];
int cnt;
int n,m;
void init(){
   memset(head,-1,sizeof(head));
   cnt=0;
}

void addedge(int u,int v,double w){
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

bool SPFA(int start){
    memset(vis,false,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    memset(num,0,sizeof(num));
    for(int i=1;i<=n;i++)
        dist[i]=INF;
    vis[start]=true;
    dist[start]=1;
    num[start]=1;
    queue<int> q;
    q.push(1);
    while(!q.empty()){
       int u=q.front();
       q.pop();
       vis[u]=false;
       for(int i=head[u];~i;i=edge[i].next){
             int v=edge[i].v;
             if(dist[v]<(dist[u]*edge[i].w)){
                  dist[v]=dist[u]*edge[i].w;
                  pre[v]=u;
                  if(!vis[v]){
                    vis[v]=true;
                    q.push(v);
                    if(++num[v]>n&&dist[v]>1) return true;
                  }
             }
       }
    }
    return false;
}

int main(){
  int Case=1;
   while(cin>>n){
       if(n==0) break;
       string s;  
       for(int i=1;i<=n;i++){
           cin>>s;  //节点由字符串改为编号
           node.insert(pair<string,int>(s,i));
       }
       cin>>m;
       init()for(int i=1;i<=m;i++){
         string s1,s2;
         double w;
         cin>>s1>>w>>s2;
          int u=node.find(s1)->second;
          int v=node.find(s2)->second;
          addedge(u,v,w);
       }
       bool flag;
       for(int i=1;i<=n;i++){
           flag=SPFA(i);
           if(flag==true) break;
       }
       if(flag==true){
           printf("Case %d: Yes\n",Case++);
       }
       else
         printf("Case %d: No\n",Case++);
      node.clear();
   }
}

问题 B: 虫洞
题目描述
耳机楼里有很多教室,这些教室由双向走廊连接。另外,还存在一些单向的秘密通道,通过它们可以回到过去。现在有 N (1 ≤ N ≤ 500) 个教室,编号 1…N M (1 ≤ M ≤ 2500) 条走廊,和 W (1 ≤ W ≤ 200) 条秘密通道。
DY在养猫之余,还是一个时间旅行爱好者。她希望从一间教室出发,经过一些走廊和秘密通道,回到她出发之前的某个时间。
共有F (1 ≤ F ≤ 5) 组数据。对每组数据,判断DY是否有回到过去的可能性。不存在耗时超过10000秒的走廊,且不存在能带DY回到10000秒之前的秘密通道。
输入
首先是一个整数F,表示接下来会有F组数据。
每组数据第1行:分别是三个空格隔开的整数:N,M和W
第2行到M+1行:三个空格分开的数字(S,E,T)描述双向走廊:从S到E需要耗费T秒。两个教室可能由一个以上的路径来连接。
第M +2到M+ W+1行:三个空格分开的数字(S,E,T)描述秘密通道:从S到E可以使时间倒流T秒。
输出
F行,每行对应一组数据。 每组数据输出单独的一行,” YES”表示能满足要求,”NO”表示不能满足要求。
样例输入
2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8
样例输出
NO
YES

#include<bits/stdc++.h>
using namespace std;
const int emaxn=3000;
const int maxn=500+5;
const int INF=0x3f3f3f3f;
struct edge{
   int u,v,w,next;
}edge[emaxn];

int head[maxn];
int dist[maxn];
int num[maxn];
bool vis[maxn];
int pre[maxn];
int cnt,n;
void init(){
   memset(head,-1,sizeof(head));
   cnt=0;
}

void addedge(int u,int v,int w){
  edge[cnt].u=u;
  edge[cnt].v=v;
  edge[cnt].w=w;
  edge[cnt].next=head[u];
  head[u]=cnt++;
}

bool SPFA(int start){
   memset(dist,INF,sizeof(dist));
   memset(num,0,sizeof(num));
   memset(vis,false,sizeof(vis));
   memset(pre,-1,sizeof(pre));
   dist[start]=0;
   vis[start]=true;
   num[start]=1;
   queue<int> q;
   q.push(start);
   while(!q.empty()){
      int u=q.front();
      q.pop();
      vis[u]=false;
      for(int i=head[u];~i;i=edge[i].next){
          int v=edge[i].v;
          if(dist[v]>dist[u]+edge[i].w){
              dist[v]=dist[u]+edge[i].w;
          //    pre[v]=u;
              if(!vis[v]){
                vis[v]=true;
                q.push(v);
                if(++num[v]>n) return true;  
              }
          }
      }
   }
   return false;
}

int main(){
   int f,m,w;
   cin>>f;
   while(f--){
       cin>>n>>m>>w;
       init();
       int s,e,t;
       for(int i=1;i<=m;i++){
          cin>>s>>e>>t;
          addedge(s,e,t);
          addedge(e,s,t);
       }
       for(int i=1;i<=w;i++){
         cin>>s>>e>>t;
         addedge(s,e,-t);
       }
    //   for(int i=1;i<=n;i++){
    //      for(int j=head[i];~j;j=edge[j].next)
      /*         printf("%d-> %d \n",edge[j].u,edge[j].v);  */
  //     }
       bool flag;
      for(int i=1;i<=n;i++){
       flag=SPFA(i);
         if(flag==true) break;
       }
       if(flag==true)
         printf("YES\n");
       else
        printf("NO\n");
   }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值