【原创】图论常用算法模板(1)

【存储方式】

(1)邻接矩阵

空间复杂度O(N^2),简单来说就是一个二维数组G[N][N],G[i][j]为INF表示i->j没有边,否则表示边的长度.

遍历查找与一个点相连的所有边的时间复杂度为O(N).太慢了,而且空间消耗太大。

基本用来卖萌。

(2)链式前向星

基本上和邻接表没什么区别,而且也很容易理解。(这里不阐述原理,只给出代码)。

int N,M;        //分别表示点数、边数
int head[N];    //第i个点的第一条边的标号   
int next[M];    //与某个点相连的某条边的下一条边标号
int end[M];     //某条边所指向的点的标号
int len[M];     //某条边的长度
int ind=2;      //当前边的标号,初始为2有利于网络流等算法
void addedge(int a,int b,int _len)
{
    int q = ind++;
    end[q] = b;
    len[q] = _len;
    next[q] = head[a];
    head[a] = q;
}

//遍历点x
for(register int tmp = head[x] ; tmp ; tmp = next[tmp])
{
	......
}

自行脑补吧。。。

【最短路算法】

Dijkstra算法(加入堆优化) O(nlogn)  (单源最短路、不适用于负权边)

思想就是每次从不在最短路的点集中选出距源点距离最短的点,然后把这个点加入最短路点集,然后用这个点维护与它相连的(指向的)点的距源点距离。这样进行n次使得图中所有的点都在最短路点集中。开始时源点的dis值为0,其余点的dis值为INF.

选出距源点距离最短的点这一步本来需要O(n)的遍历,不过使用堆优化可以降至O(logn).总时间复杂度为O(nlogn).

更邪恶一点的C++党用优先级队列直接水。(具体怎么用往下看)

*水一发*->NOI2010【海拔】

不知题意的百度一下。

本弱渣打眼一看立马蒙圈,而某些大神狗眼一看就知道海拔只有0,1。。。狗眼再一看就知道海拔为连续0,1块。(不明觉厉)然后直接最小割->最大流,以左上角为源点,右下角为汇点求一下最大流即可。不过这样只有80-90。

为什么80-90呢,因为最后的点的数目比较多。会TLE一些点。

观察可得这图构造得比较和谐,然后可以构造它的对偶图(见08年国家队论文day2)转化为最短路,细节到那里去脑补。

说一下构图的方法。

由于对偶图是以平面图的面来做点的,所以对偶图有N^2+2个点,多出来那两个是源点和汇点。

边的方向?

本蒟蒻画了一幅图。。。


以左下角为起点,右上角为终点,中间的格子为点,建图。

粉色是实际边的方向(总是由0块指向1块),红色是连边的方向(由起点指向终点)。

具体来说,下边向右连,左边向下连,右边向上连,上边向左连。

然后直接水一发最短路。

由于是稠密图,spfa不幸超时。。。只好用堆优化dijkstra...

代码如下:

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
struct Node
{
    int ord , dist;
    Node(int _ord = 0, int _dist = 0):ord(_ord),dist(_dist){}
    bool operator  < (const Node &b) const
    {
        return dist > b.dist;
    }
};
priority_queue<Node> q;
int head[250010] , next[1100000] , end[1100000] , len[1100000] , ind;
void addedge(int a , int b , int _len)
{
    int q = ++ind;
    end[q] = b;
    len[q] = _len;
    next[q] = head[a];
    head[a] = q;
}
int S , T , n;
int mindis[250010];
void dijkstra()
{
    memset(mindis , 0x3f , sizeof(mindis));
    mindis[S] = 0;
    q.push(Node(S , mindis[S]));
    while(!q.empty())
	{
        Node X = q.top();
        q.pop();
        int ins = X.ord;
        if(X.dist > mindis[ins]) continue;
        for(register int tmp = head[ins]; tmp ; tmp = next[tmp])
        {
            if(mindis[end[tmp]] > mindis[ins] + len[tmp])
            {
                mindis[end[tmp]] = mindis[ins] + len[tmp];
                q.push(Node(end[tmp] , mindis[end[tmp]]));
            }
        }
    }
}
int main()
{
    register int i,j;
    scanf("%d",&n);
    S = 0 , T = n * n + 1;
    int x;
     
    //west->east
    for(i = 1 ; i <= n ; ++i)
    {
        scanf("%d",&x);
        addedge(i , T , x);
    }
    for(i = 1 ; i < n ; ++i)
    {
        for(j = 1 ; j <= n ; ++j)
        {
            scanf("%d",&x);
            addedge(i * n + j , (i - 1) * n + j , x);
        }
    }
    for(i = 1 ; i <= n ; ++i)
    {
        scanf("%d",&x);
        addedge(S , n * (n - 1) + i , x);
    }
     
     
    //north->south
    for(i = 1 ; i <= n ; ++i)
	{
		scanf("%d",&x);
		addedge(S , (i - 1) * n + 1 , x);
		for(j = 1 ; j < n ; ++j)
		{
			scanf("%d",&x);
			addedge((i - 1) * n + j , (i - 1) * n + j + 1 , x);
		}
		scanf("%d",&x);
		addedge(i * n , T , x);
	}
     
     
    //east->west
    for(i = 1 ; i <= n ; ++i)
    {
        scanf("%d",&x);
        addedge(T , i , x);
    }
    for(i = 1 ; i < n ; ++i)
    {
        for(j = 1 ; j <= n ; ++j)
        {
            scanf("%d",&x);
            addedge((i - 1) * n + j , i * n + j , x);
        }
    }
    for(i = 1 ; i <= n ; ++i)
    {
        scanf("%d",&x);
        addedge(n * (n - 1) + i , S , x);
    }
     
     
    //south->north
    for(i = 1 ; i <= n ; ++i)
	{
		scanf("%d",&x);
		addedge((i - 1) * n + 1 , S , x);
		for(j = 1 ; j < n ; ++j)
		{
			scanf("%d",&x);
			addedge((i - 1) * n + j + 1 , (i - 1) * n + j , x);
		}
		scanf("%d",&x);
		addedge(T , i * n , x);
	}
 
    dijkstra();
    printf("%d",mindis[T]);
    return 0;
}

spfa算法  时间复杂度O(kE)  对于稀疏图可证k<=2,对于稠密图。。。

很吊的最短路算法,效率比较高而且很好写,能够灵活地维护一些信息。(学名叫做“松弛技术”)

不过对于稠密图效率大大降低。

先来看一看朴素的单源最短路径长度。

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int Maxn = 1001;
int dis[Maxn];
bool inqueue[Maxn];
void sqfa(int S) //起点为S
{
	memset(dis , 0x3f , sizeof(dis));    //保证2倍不会爆int的大数
	dis[S] = 0;
	memset(inqueue , 0 , sizeof(inqueue));
	inqueue[S] = 1;
	queue<int> q;
	q.push(S);
	while(!q.empty())
	{
		int ins = q.front();
		q.pop();
		inqueue[ins] = 0;    //不要忘了!!
		for(int tmp = head[ins] ; tmp ; tmp = next[tmp]) //链式前向星
		{
			int to = end[tmp];
			if(dis[to] > dis[ins] + len[tmp])
			{
				dis[to] = dis[ins] + len[tmp];
				if(!inqueue[to])
				{
					inqueue[to] = 1;
					q.push(to);
				}
			}
		}
	}
}
调用spfa()后,dis[]数组存放每一个点到起点的距离。

(建议你们试一下poj1062  体会一下)

spfa是不怕负权边的,不过对于一些没有最短路的图来说,就会陷入死循环。这些图有负权回路。判定负环的方法很简单,依旧利用spfa求解最短路。假设图中有N个点,记录每个点入队被松弛的次数,如果一个点被入队松弛了超过N次,则图中一定存在负权回路。(显然可证)


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值