1. 程式人生 > >各演算法時間複雜度總結

各演算法時間複雜度總結

僅供自己參考。

圖論演算法:

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(?)