各算法时间复杂度总结

仅供自己参考。

 

图论算法:

Dijkstra点对点最短路:

    for(i=1;i<=n;i++)
 	{
        min=MAX;
        for(j=1;j<=n;j++)
        {
            if(!mark[j] && dist[j]<min)
            { //取出不在mark里的最小的dist[i]
                min=dist[j];
                pos=j;//标记
            }
        }
            
        if(min==MAX)//已经不能通了
            break;

        mark[pos]=1;//把K加进来

        //做松弛操作
        for(j=1;j<=n;j++)
        {
            if(!mark[j] && dist[j]>dist[pos]+map[pos][j]) //start->j or start->pos,pos->j 
            {
                dist[j]=dist[pos]+map[pos][j];//这步跟prim算法有点不同
            }
        }
    }

n代表有多少个点,复杂度是O(n²)。

对dijkstra用优先队列优化则有(用邻接表建图)

void Dijkstra()
{
    for(int i = 0; i <= n; i++)
	dis[i] = INF;
    dis[s] = 0;
    priority_queue <node> q;
    q.push(node(s, dis[s]));
    while(!q.empty())
    {
        node u = q.top();
	q.pop();
        for(int i = 0; i < eg[u.point].size(); i++)
        {
            node v = eg[u.point][i];
            if(dis[v.point] > u.val + v.val)
            {
                dis[v.point] = u.val + v.val;
                q.push(node(v.point, dis[v.point]));
                front[v.point] = u.point;
            } 
        }
    }
}

复杂度为O(E*logV)。E为边数,V为点数。

 

Bellman—Ford单源最短路算法(可带负权,但不能有负权环)

bool Bellman_Ford()
{
	for(int i = 1; i <= nodenum; ++i) 
		dis[i] = (i == original ? 0 : MAX);
	for(int i = 1; i <= nodenum - 1; ++i)
		for(int j = 1; j <= edgenum; ++j)
			if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) 
			{
				dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;
				pre[edge[j].v] = edge[j].u;
			}
	bool flag = 1; //判断是否含有负权回路
	for(int i = 1; i <= edgenum; ++i)
		if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
		{
			flag = 0;
			break;
		}
		return flag;
}

nodenum就是点的个数,edgenum就是边的个数,从上面这段代码可以明显看出,时间复杂度应该为O(VE+E),V为点的个数,E为边的个数。这种算法有个很严重的问题,就是冗余量太大,进入两个for循环那部分很多时候都是无法操作的。所以有了一种算法叫SPFA,用队列和一个数组标记来去掉那么多冗余的部分。

SPFA单源最短路算法(可带负权,不可有负环)

这种算法的话,复杂度理论上说是O(K*E)k是进队列的次数(一般认为小于等于2),E是边数,他最坏的情况依然会回到Bellman的复杂度。对于稀疏图速度不错,对于稠密图会导致进队列次数增加不建议使用。

bool spfa(int s, int e)
{
	int u, v;
	for(int i = 0; i <= n; i++)
		dis[i] = INF;
	memset(flag, 0, sizeof(flag));
	dis[s] = 0;
	minflow[s] = INF;
	flag[s] = 1;
	queue <int> q;
	q.push(s);
	while(!q.empty())
	{
		u = q.front();
		q.pop();
		flag[u] = 0;
		for(int i = head[u]; ~i; i = front[i])
		{
			v = to[i];
			if(flow[i] && dis[v] > dis[u] + cost[i])
			{
				dis[v] = dis[u] + cost[i];
				par[v] = (make_pair(u, i));
				minflow[v] = min(minflow[u], flow[i]);
				if(!flag[v])
				{
					flag[v] = 1;
					q.push(v);
				}
			}
		}
	}
	if(dis[e] == INF)
		return 0;
	return 1;
}

spfa我一般采用链式前向星的建图方法,比如上面。当然什么邻接矩阵,邻接表都是可以的。

 

Floyd-Warshall全局最短路算法:

void floyd_warshall(int n)
{
    int i,j,k;
    for (k=1;k<=n;k++)
        for (i=1;i<=n;i++)
            for (j=1;j<=n;j++)
                if (mat[i][k] + mat[k][j] < mat[i][j])
                	mat[i][j] = mat[i][k] + mat[k][j];           
}
floyd复杂度很高,n依然是代表有多少个点,时间复杂度高达O(n^3)。这样可以算算,当有1000个点的时候,运算量是1e9,适用范围就很局限了。

该算法其实很像矩阵乘法。

网络流算法中,EK和Dinic看上去复杂度相同,都是O(n^2 * m),但是Dinic递推到某些点就完成了开始回溯,可以省去遍历很多情况,所以比较快,已经够用。

Dinic算法如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm> 
#include <queue>
using namespace std;
const int MAX = 0x3f3f3f3f;
int G[205][205];
int dis[205];
int n, m, ans;
bool bfs()
{
	int now;
	memset(dis, -1, sizeof(dis));
	queue <int> q;
	dis[1] = 0;
	q.push(1);
	while(!q.empty())
	{
		now = q.front();
		q.pop();
		for (int i = 1; i <= n; i++)
		    if (dis[i] < 0 && G[now][i] > 0)
		    {
		        dis[i] = dis[now] + 1; 
		        q.push(i);
		    }
    }
    if(dis[n] > 0)
        return 1;
    else
        return 0;
}

int find(int x, int low)
{
    int i, tmp = 0;
    if (x == n)
		return low;
    for (i = 1; i <= n; i++)
	    if (G[x][i] > 0 
	     && dis[i] == dis[x] + 1 
	     &&(tmp = find(i, min(low, G[x][i]))))
	    {
	       G[x][i] -= tmp;
	       G[i][x] += tmp;
	       return tmp;
	    }
    return 0;
}
int main()
{
    int i, j, u, v, flow, tmp;
    while (scanf("%d%d", &m, &n)!=EOF)
	{
	    memset(G, 0, sizeof(G));
	    for (i = 1; i <= m; i++)
	    {
	        scanf("%d%d%d", &u, &v, &flow);
	        G[u][v] += flow;
	    }
	    ans = 0;
	    while (bfs())
	    {
	        while(tmp = find(1, MAX))
				ans += tmp;
	    }
	    printf("%d\n", ans);
    }
}

最小费用最大流算法:由于费用可以为负数,可以要用spfa。复杂度为O(E * KE),KE是spfa的复杂度,E为点数,就是前面bfs的n*m的复杂度。

bool spfa(int s, int e)
{
	int u, v;
	for(int i = 0; i <= n; i++)
		dis[i] = INF;
	memset(flag, 0, sizeof(flag));
	dis[s] = 0;
	minflow[s] = INF;
	flag[s] = 1;
	queue <int> q;
	q.push(s);
	while(!q.empty())
	{
		u = q.front();
		q.pop();
		flag[u] = 0;
		for(int i = head[u]; ~i; i = front[i])
		{
			v = to[i];
			if(flow[i] && dis[v] > dis[u] + cost[i])
			{
				dis[v] = dis[u] + cost[i];
				par[v] = (make_pair(u, i));
				minflow[v] = min(minflow[u], flow[i]);
				if(!flag[v])
				{
					flag[v] = 1;
					q.push(v);
				}
			}
		}
	}
	if(dis[e] == INF)
		return 0;
	return 1;
}

void Min_Cost_Max_Flow(int s, int e)
{
	int ans = 0, p;
	while(spfa(s, e))
	{
		p = e;
		while(par[p].first != s)
		{
			flow[par[p].second] -= minflow[e];
			flow[par[p].second^1] += minflow[e];
			p = par[p].first;
		}
		ans += dis[e];
	}
	cout << ans << endl;
}


二分图中,对于无权的,可以使用匈牙利算法,匈牙利算法其实就是网络流的压缩版本。

匈牙利算法:复杂度O(n*m),每个点都需要去寻找一次匹配,匹配过程最坏的情况就是全部边都要变,所以为n * m。

bool dfs(int u)
{
    int v;
    for(v = 0; v < vN; v++)
        if(G[u][v] && !used[v])
        {
            used[v] = true;
            if(linker[v] == -1 || dfs(linker[v]))
            {
                linker[v] = u;
                return true;
            }    
        }  
    return false;  
}    
int hungary()
{
    int res = 0;
    int u;
    memset(linker, -1, sizeof(linker));
    for(u = 0; u < uN; u++)
    {
        memset(used, 0, sizeof(used));
        if(dfs(u))  
			res++;
    } 
    return res;   
}     
对于有权的二分图,匈牙利算法已经不适用,要使用KM算法,这个比较难懂。

KM算法:复杂度O(?)




  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值