各演算法時間複雜度總結
阿新 • • 發佈:2019-01-01
僅供自己參考。
圖論演算法:
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(?)