最短路&拓扑排序&最小生成树

最短路问题

图里面比较经典的问题,这里总结几种求最短路径的方法,1.bellman-ford(贝尔曼), 2.floyd(弗洛伊德),3.dijkstra(迪杰斯特拉),loyd(弗洛伊德)比较好理解,但是很暴力,大一点的数据就会爆掉了。dijkstra(迪杰斯特拉)是一种贪心的算法在里面。

bellman-ford
1.用d数组存储从出发点到顶点i的最短距离。
2.d[ i ]=min{ d[ j ]+从j 到 i 的边的权值,d[ i ]};不断更新

struct{
int from;
int to;
int cost;
}g[maxn];
int d[maxn];
int n,m;
void bellman-ford(int s)
{
	memset(d,inf,sizeof(d));
	d[s]=0;
	while(true)
	{
	 	bool update=false;
	 	for(int i=0;i<m;i++)
	 	{
	 		if(d[g[i].from]!=inf&&d[g[i].to]>d[g[i].from]+g[i].cost)
	 		{
	 		  d[g[i].to]=d[g[i].from]+g[i].cost;
	 		  update=true;
	 		}
	 	}
	 	if(!update)
	 	break;
	}
	
}

3.还可以用来检查负环(负圈:总长度小于0的有向环路。)的存在。遍历整个图更新的时候,最短路不会经过一个点两次,即,最多执行while那个循环,n-1(点数减一次)。所以可以一开始令一个数组的初值为0。

bool find_furoot()
{
	memset(d,0,sizeof(d));
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(d[g[j].to]>d[g[j].from]+g[j].cost)
			{
				d[g[j].to]=d[g[j].from]+g[j].cost	;
				if(i==n-1)//如果最后一次还进行了更新,说明有负圈
				return true;
			}
				
		}
	}
	return false;
}

floyed
把所有点,和边存入一个二维数组里面,三重循环找优解。

int maze[maxn][maxn];
memset(maze,inf,sizeof(maze));
for(int k=0;k<maxn;k++)
  for(int i=0;i<maxn;i++)
  	for(int j=0;j<maxn;j++)
  	  if(maxn[i][j]>maxn[i][k]+maze[k][j])
  	 	 maxn[i][j]=maxn[i][k]+maze[k][j];

然后二维数组里面存的就是 i 到 j 的最短路。

dijkstra
1.需要一个maze [ ][ ] 二维数组来存下整个图
2.一个vis [ ] 数组来看是否访问过
3.一个dis[ ]更新来求最短路

int maze[maxn][maxn];
int vis[maxn];
int dis[amxn];
void init()
{
	memset(maze,inf,sizeof(maze));
	for(int i=0;i<maxn;i++)
	{
		maze[i][i]=0;
	}
	
	memset(dis,inf,sizeof(dis));
}
void dij(int n)
{
	memset(vis,0,sizeof(vis));
	for(int i=0;i<maxn;i++)
	  dis[i]=maze[n][i];//先把图上的信息更新在dis里面
	  vis[n]=1;//标记已经访问过n这个点
	  for(int k=0;k<maxn;k++)
	  {
	     int minn=inf,temp; //寻找最短的那一条路,并且把点的位置记录下来
	  	for(int i=0;i<maxn;i++)
	  	{
	  		if(!vis[i]&&dis[i]<minn)
	  		{
	  			minn=dis[i];
	  			temp=i;
	  		}
	  	}	
	  	vis[temp]=1;//比较,更新dis
	  	
	  	for(int j=0;j<maxn;j++)
	  	{
	  		if(dis[j]>maze[temp][j]+dis[temp])
	  		dis[j]=maze[temp][j]+dis[temp];
	  	}
	  }	
}
int main()
{
	init();
	//输入图的相关信息
	dij();
	printf("%d",maze[a][b]);
	
}


拓扑排序

做一件事情有先后条件,某一件事必须在做完另外某一件事情之后才可以做。但也有一种可能就是,成环了。所以一般这个函数我喜欢写成bool 类型的
举一个成环的例子:
a---->b 意味着先做a再做b ; b---->c 意味着先做b再做c ;意味着如果这个三件事可以好好做的话,那么一定先做a再做c,但是如果此时还有一个条件的话(c----->a)那么意味着三者成环,没有顺序可以完成这个。

实现步骤:
1.找一个入度(比这件事情要前面完成事情的数量)为0的点
2.删除这个点的同时把以这个点的所有出入点的度减一
3.直到不存在入度为0 的点为止

存图的方法:
1.邻接矩阵
2.邻接表(一个struct 加一个head数组存,用stl里面的vector)
HDU 2647 reward
邻接表实现的拓扑排序

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <string>
#include <cstring>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define inf 0x3f3f3f3f
#define ll long long
#define maxn 10003
using namespace std;
int bi[maxn];
int head[maxn];
int sum[maxn];
int n,m,num=0,ans=0;
struct node{
    int to;
    int next;
}edge[maxn*2];
void init()
{
    memset(head,-1,sizeof(head));
    memset(bi,0,sizeof(bi));
    memset(sum,0,sizeof(sum));
    num=0;
    ans=0;
}
void add()
{
    int a,b;

        scanf("%d%d",&b,&a);
        edge[num].to=b;
        edge[num].next=head[a];
        head[a]=num++;
        bi[b]++;

}
bool pt()
{
    int cnt=0;
    queue<int> que;
    while(!que.empty())
        que.pop();
    for(int i=1;i<=n;i++)
    {
        if(bi[i]==0)
        {
            que.push(i);
            sum[i]=888;
        }

    }
    while(!que.empty())
    {
        int now=que.front();
        que.pop();
        cnt++;
        bi[now]=-1;
        ans+=sum[now];
        for(int i=head[now];i!=-1;i=edge[i].next)
        {
            int d=edge[i].to;
            if(--bi[d]==0)
            {
                que.push(d);
                sum[d]=sum[now]+1;
            }
        }
    }
    if(cnt==n)
        return true;
    else
        return false;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=0;i<m;i++)
        {
            add();
        }

    if(pt())
        printf("%d\n",ans);
    else
        printf("-1\n");
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值