圖論演算法
阿新 • • 發佈:2021-01-25
技術標籤:演算法筆記演算法圖論最小生成樹單源最短路徑拓撲排序
圖論演算法
1. 最小生成樹(Kruscal演算法)
/**** **** **** **** **** ****
- Function Name : 最小生成樹(Kruscal演算法)
- Description : ZJU 1203
**** **** **** **** **** ****/
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
struct struct_edges
{
int bv,tv; //bv 起點 tv 終點
double w; //權值
};
struct_edges edges[10100]; //邊集
struct struct_a
{
double x;
double y;
};
struct_a arr_xy[101];
int point[101],n,e; //n 頂點數, e 邊數(注意是無向網路)
double sum;
int kruscal_f1(int point[], int v)
{
int i = v;
while(point[i] > 0) i = point[i];
return i;
}
bool UDlesser(struct_edges a, struct_edges b)
{return a.w < b.w;}
void kruscal() //只需要準備好n,e,遞增的邊集edges[]即可使用
{
int v1,v2,i,j;
for(i=0; i<n ;i++) point[i]=0;
i = j = 0;
while(j<n-1 && i<e) {
v1 = kruscal_f1(point, edges[i].bv);
v2 = kruscal_f1(point, edges[i].tv);
if(v1 != v2) {
sum += edges[i].w; //注意sum初始為0
point[v1]=v2;
j++;
}
i++;
}
}
int main()
{
int k,i,j;
cin>>n;
k=0;
while(n != 0) {
sum=0;
k++;
for(i=0; i<n ;i++)
cin>>arr_xy[i].x>>arr_xy[i].y;
e=0;
for(i=0; i<n ;i++) //從0開始計數
for(j=i+1; j<n ;j++) //注意是無向網路
{
if(i == j) continue;
edges[e].bv=i;
edges[e].tv=j;
edges[e].w=sqrt((arr_xy[i].x-arr_xy[j].x)*(arr_xy[i].x-arr_xy[j].x)+(arr_xy[i].y-arr_xy[j].y)*(arr_xy[i].y-arr_xy[j].y));
e++;
}
sort(edges,edges+e,UDlesser); //得到一個遞增的邊集,注意是從0開始計數
kruscal();
printf("Case #%d:\n",k); //cout<<"Case #"<<k<<":"<<endl;
printf("The minimal distance is: %.2f\n",sum); //輸出sum
cin>>n;
if(n != 0) printf("\n");
}
}
2. 最小生成樹(Prim演算法)
/**** **** **** **** **** ****
- Function Name : 最小生成樹(Prim演算法)
- Description : ZJU 1203 Swordfish O(N^2)
**** **** **** **** **** ****/
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
double sum, arr_list[101][101], min;
int i, j, k=0, n;
struct struct_a
{
float x;
float y;
};
struct_a arr_xy[101];
struct struct_b
{
int point;
float lowcost;
};
struct_b closedge[101];
void prim(int n) //prim 需要準備:n頂點數 arr_list[][]頂點的鄰接矩陣也是從0開始計數
{
int i,j,k;
k=0;
for(j=0; j<n ;j++) {
if(j != k) {
closedge[j].point = k;
closedge[j].lowcost = arr_list[k][j];
}
}
closedge[k].lowcost=0;
for(i=0; i<n ;i++) {
min=10000;
for(j=0; j<n ;j++) {
if (closedge[j].lowcost != 0 && closedge[j].lowcost < min) {
k = j;
min = closedge[j].lowcost;
}
}
sum += closedge[k].lowcost; //不要改成sum+=min; sum即為所求值
closedge[k].lowcost = 0;
for(j=0; j<n ;j++) {
if(arr_list[k][j] < closedge[j].lowcost) {
closedge[j].point = k;
closedge[j].lowcost = arr_list[k][j];
}
}
}
}
/*
arr_list[][]= Wij 如果Vi, Vj有邊
0 如果i=j
無限大 如果沒有邊
*/
int main()
{
cin>>n;
while(n != 0) {
sum=0;
k++;
for(i=0; i<n ;i++)
cin>>arr_xy[i].x>>arr_xy[i].y;
for(i=0; i<n ;i++)
for(j=0; j<n ;j++) //得到鄰接矩陣arr_list[][]
arr_list[i][j]=arr_list[j][i]=sqrt((arr_xy[i].x-arr_xy[j].x)*(arr_xy[i].x-arr_xy[j].x)+(arr_xy[i].y-arr_xy[j].y)*(arr_xy[i].y-arr_xy[j].y));
prim(n);
cout<<"Case #"<<k<<":"<<endl;
printf("The minimal distance is: %.2f\n",sum);
cin>>n;
if(n!=0) printf("\n");
}
}
3. 單源最短路徑(Bellman-ford演算法)
/**** **** **** **** **** ****
- Function Name : 單源最短路徑(Bellman-ford演算法)
- Description : 可允許有負權
**** **** **** **** **** ****/
#include <stdio.h>
#define MAX 100
#define MAXNUM 1000000
typedef struct graphnode
{
int vexnum; //頂點數
int arcnum; //邊數
int gra[MAX][MAX]; //圖
}Graph;
Graph *G;
//arc陣列中儲存的第一個頂點到其他頂點的最短路徑
//結果存在dis陣列中
int dis[MAX];
int arc[MAX][MAX];
void bellman(Graph *G)
{
int i,j;
bool sign;
for(i=0; i < G->vexnum ;i++) dis[i]=MAXNUM;
dis[1] = 0;
sign = true;
for(i=1; i < G->vexnum ;i++) {
sign = false;
for(j=0; j < G->arcnum ;j++) {
if(dis[ arc[j][0] ] < MAXNUM
&& dis[ arc[j][1] ] > dis[ arc[j][0] ] + G->gra[ arc[j][0] ][ arc[j][1] ])
{
dis[ arc[j][1] ]=dis[ arc[j][0] ] + G->gra[ arc[j][0] ][ arc[j][1] ];
sign = true;
}
}
}
return;
}
4. 單源最短路徑(Dijkstra演算法)
/**** **** **** **** **** ****
- Function Name : 單源最短路徑 (Dijkstra演算法)
- Description : 貪心, O(N^2), 不能有負權
**** **** **** **** **** ****/
int matrix[200][200],n; //matrix[][], 30000表示無限大,即無邊.否則為有邊,其值為邊的權值
void Dijkstra(int x,int y) //起點Vx 終點Vy
{
int i,j,k,path[40000],mark[40000];
int min,dist[40000];
for(i=1;i<=n;i++) {
mark[i] = 0;
dist[i] = matrix[x][i];
path[i] = x;
}
mark[x] = 1;
do {
min=30000;
k=0;
for(i=1;i<=n;i++)
if(mark[i]==0 && dist[i]<min) {
min = dist[i];
k = i;
}
if(k) {
mark[k] = 1;
for(i=1;i<=n;i++)
if(matrix[k][i]<30000 && min+matrix[k][i]<dist[i]) {
dist[i] = min + matrix[k][i];
path[i] = k;
}
}
}while(k);
cout<<dist[y]<<endl; //dist[y] 的值就是從Vx 到 Vy 的最短路徑值
//如果希望得到路徑,加入如下程式碼:
do {
cout<<k<<"<--";
k = path[k];
}while(k!=x);
cout<<x<<endl;
}
5. 全源最短路徑(Folyd演算法)
/**** **** **** **** **** ****
- Function Name : 全源最短路徑(Folyd演算法)
- Description : DP, O(N^3)
**** **** **** **** **** ****/
//初始化
//min_graph[i][j]=graph[i][j];
//path[i][j]=j;
void Floyd()
{
int i,j,k;
for(k=0;k<vertex_number;k++) {
for(i=0;i<vertex_number;i++) {
for(j=0;j<vertex_number;j++) {
if((graph[i][k]==-1) || (graph[k][j]==-1)) continue;
if((min_graph[i][j]==-1) || (min_graph[i][j] > graph[i][k]+graph[k][j]))
{
min_graph[i][j] = graph[i][k]+graph[k][j]; /*最短路徑值*/
path[i][j] = k; /*最短路徑*/
}
}
}
}
}
6. 拓撲排序
/**** **** **** **** **** ****
- Function Name : 拓撲排序
**** **** **** **** **** ****/
//degree[] 每個結點的入度
//f[] 每個結點所在的層
void Toplogical_sort()
{
int i,j;
bool p=true;
top=0;
while(p) {
p=false;
top++;
for(i=1;i<=n;i++)
if(degree[i]==0) {
p=true;
f[i]=top;
}
for(i=1;i<=n;i++)
if(f[i]==top) {
for(j=1;j<=n;j++)
if(map[i][j]) degree[j]--;
degree[i]=-1;
}
}
top--;
}
7. 網路預流和最大流
int rel[1000][10000]; //全域性變數
int pre[1000];
//計算網路流
//如果是二分圖的匹配, 可以先對其進行網路預流以簡化後續的查詢
int pre_flow(int n,vector<int> * v)
{
int ret = 0;
int i,j,t,t1;
for(i = 0 ; i < v[0].size() ; i++){
t = v[0][i]; //t是與節點0相鄰接的點
for(j = 0 ; j < v[t].size() ; j++){
t1 = v[t][j]; //與t相鄰接的點
if(rel[t1][n - 1] > 0){
ret++;
rel[0][t]--, rel[t][0]++;
rel[t][t1]--, rel[t1][t]++;
rel[t1][n - 1]--, rel[n - 1][t1]++;
break;
}
}
}
return ret;
}
/*
網路中求最大流
引數含義: n代表網路中節點數,第0節點為源點, 第n-1節點為匯點
rel是個二維陣列, rel[i][j]代表從節點i到節點j的流量
v[]是一個節點陣列, v[i]包含與節點i相鄰接的所有節點
返回值: 最大流量
*/
int max_flow(int n,vector<int> * v)
{
int ret = 0,i;
int t,t1,tm;
queue<int> q;
const int Infinite = 2000000000;
while(1){
for(t = 0 ; t < n ; t++) pre[t] = -1;
while(!q.empty()) q.pop();
q.push(0);
while(!q.empty()){ //find a augmenting path using breath-first search
t = q.front();
q.pop();
if(t == n - 1) break; //到達匯點
for(i = 0 ; i < v[t].size() ; i++){ //對於t相鄰接的所有點查詢可行路徑
t1 = v[t][i];
if(rel[t][t1] > 0 && pre[t1] == -1){
pre[t1] = t;
q.push(t1);
}
}
}
if(q.empty() && t != n - 1) break;
tm = Infinite; //此處尋找路徑最小值在二分圖中可省略
while(t != 0){ //find the minimal num in the path
t1 = pre[t];
if(rel[t1][t] < tm) tm = rel[t1][t];
t = t1;
}
// tm = 1; //二分圖中
t = n - 1;
while(t != 0){ //change the relation
t1 = pre[t];
rel[t1][t] -= tm;
rel[t][t1] += tm;
t = t1;
}
ret += tm;
}
return ret;
}
8. 網路最小費用最大流
/**** **** **** **** **** ****
網路中最小費用最大流
引數含義: np代表網路中的總節點數, v是網路節點的鄰接表
cost為最後求得的最小費用, mf為求得的最大流
演算法: 初始最小費用及最大流均為0,不斷尋找可增廣路
增廣路對應的單位費用最小並且可流
修改殘留網路及cost,mf. 直到無可增廣路為止。
**** **** **** **** **** ****/
const int Max = 200000000;
vector<int> v[110]; //儲存每個節點的鄰接點
int flow[110][110]; //flow[i][j]代表由i節點到j節點的可行流量
int fcost[110][110]; //fcost[i][j]代表由i節點到j節點的單位流量費用
int ct[110]; //ct[i]代表單位容量到達i節點的最小費用
int pre[110]; //可行節點的前驅節點
void min_cost_max_flow(int np,const vector<int> * v,int & cost,int & mf)
{
int t,t1,tm,i,j;
bool out;
cost = 0,mf = 0;
while(1){
for(i = 0 ; i < np ; i++) pre[i] = -1,ct[i] = Max;
ct[0] = 0;
while(1){
out = false;
for(i = 0 ; i < np ; i++){
for(j = 0 ; j < v[i].size() ; j++){
t = v[i][j];
if(flow[i][t] > 0 && ct[i] != Max && ct[i] + fcost[i][t] < ct[t]){
out = true;
ct[t] = ct[i] + fcost[i][t];
pre[t] = i;
}
}
}
if(!out) break;
}
if(ct[np - 1] != Max){
t = np - 1;
tm = Max; //此處尋找流量最小值
while(t != 0){ //find the minimal num in the path
t1 = pre[t];
if(flow[t1][t] < tm) tm = flow[t1][t];
t = t1;
}
mf += tm; //流量增加
t = np - 1;
while(t != 0){ //change the relation
t1 = pre[t];
flow[t1][t] -= tm;
flow[t][t1] += tm;
cost += tm * fcost[t1][t]; //費用增加
t = t1;
}
}
else break;
}
}
9. 網路最大流(高度標號預流推進)
/*
函式介面: int Relabel_To_Front(int s,int d)
引數含義: s為源點,d為匯點
返回值 : 網路最大流
呼叫函式前的初始化工作:ver置為網路中節點的個數,c[i][j]代表節點i到
節點j的流量,vl[i]存放i與相鄰的所有節點
其它全域性變數均初始化為零
*/
const int VEX = 405; //網路中頂點數
const int HMAX = 810; //最大高度的定義,只要大於頂點的2倍就可以了
int f[VEX][VEX]; //流量
int c[VEX][VEX]; //邊最大容量
int h[VEX]; //節點高度
int e[VEX]; //節點容量
int ver; //節點數目
vector<int> vl[VEX]; //鄰接表,vl[i]存放與i相鄰的節點
void Push(int u,int v) //流推進,由節點u推向v
{
int cf = c[u][v] - f[u][v]; //u,v邊的容量
int d = e[u] < cf ? e[u] : cf;
f[u][v] += d;
f[v][u] = -f[u][v];
e[u] -= d;
e[v] += d;
}
void Relabel(int u) //對u重新標號
{
int i,t,cf;
int hmin = HMAX;
for(i = 0 ; i < vl[u].size() ; i++){ //尋找相鄰最低點
t = vl[u][i];
cf = c[u][t] - f[u][t];
if(cf > 0 && h[u] <= h[t] && h[t] < hmin)
hmin = h[t];
}
h[u] = hmin + 1;
}
void Init_Preflow(int s) //初始化網路流,s為源點
{
int i;
int u;
h[s] = ver; //初始化高度
for(i = 0 ; i < vl[s].size() ; i++){
u = vl[s][i];
f[s][u] = c[s][u];
f[u][s] = -c[s][u];
e[u] = c[s][u];
e[s] -= c[s][u];
}
}
void Discharge(int u)
{
int i = 0;
int cf,v;
if(vl[u].size() == 0) return;
while(e[u] > 0){
if(i < vl[u].size()) {
v = vl[u][i];
cf = c[u][v] - f[u][v];
}
if(i >= vl[u].size()){
Relabel(u);
i = 0;
}
else if(cf > 0 && h[u] == h[v] + 1)
Push(u,v);
else
i++;
}
}
int Relabel_To_Front(int s,int d) //s為源點,d為匯點
{
int u,i,old_h;
list<int> l;
list<int>::iterator iter;
Init_Preflow(s);
iter = l.begin();
for(i = 0 ; i < ver ; i++){
if(i != s && i != d)
l.insert(iter,i);
}
iter = l.begin();
while(iter != l.end()){
u = *iter;
old_h = h[u];
Discharge(u);
if(h[u] > old_h){
l.erase(iter);
l.insert(l.begin(),u);
iter = l.begin();
}
iter++;
}
return e[ver - 1];
}
10. 最大團
/**** **** **** **** **** ****
- Function Name : 最大團
- Description : ZJU 1492 Maximum Clique
- 團: 指G的一個完全子圖, 該子圖不包含在任何其他的完全子圖當中
- 最大團: 指其中包含頂點最多的團
**** **** **** **** **** ****/
#include<stdio.h>
#include<string.h>
int joint[50][50];
int Size, MAX, DP[50];
bool find;
//返回-1表示鄰接頂點集已經為空集,否則返回第一個公共頂點
int reachEnd(int start, int sets[])
{
int lp;
for(lp=start; lp<Size ;lp++)
if(sets[lp]) return lp;
return -1;
}
void DFS(int Visit[], int start, int depth)
{
int loop, first, sets[50], SET[50];
memcpy(sets,Visit,Size*4);
memcpy(SET,Visit,Size*4);
if(( first=reachEnd(start,sets) ) == -1) {
if(depth > MAX) {
MAX = depth;
find = true;
}
return ;
}
while(first != -1) {
if(depth + Size - start <= MAX)//不可能找到最優解
return ;
if(depth + DP[first] <= MAX)//不可能找到最優解
return;
sets[first] = 0;
SET[first] = 0;//從鄰接頂點集中清除first頂點
for(loop=first+1; loop < Size ;loop++) //合併鄰接頂點集
if(SET[loop]==1 && joint[first][loop]==1) sets[loop]=1;
else sets[loop]=0;
DFS(sets,first,depth+1);
if(find) return ;
first = reachEnd(first,SET);//更新接點
}
}
int main()
{
int loop, lp, Visit[50];
while(scanf("%d",&Size)!=EOF && Size!=0) {
for(loop=0; loop < Size ;loop++)
for(lp=0; lp < Size ;lp++)
scanf("%d",joint[loop]+lp);
MAX=0;
for(loop=Size-1; loop >= 0 ;loop--) {
find=false;
memcpy(Visit, joint[loop], Size*4);
DFS(Visit, loop, 1);
DP[loop] = MAX;
}
printf("%d\n",DP[0]);
}
}
11. 最大二分圖匹配(匈牙利演算法)
/**** **** **** **** **** ****
- Function Name : 最大二分圖匹配(匈牙利演算法)
- Description : HDOJ 2063 過山車
- 二分圖: 指所有頂點分成集合M和N, M或N中任意兩個在同一集合中的點互不相連
- 匹配: 一組邊頂點分別在兩個集合中, 並且任意兩條邊都沒有相同頂點
- 最大匹配: 所能得到的最大的邊的個數
**** **** **** **** **** ****/
#include<cstdio>
#include<memory>
#include<vector>
using namespace std;
const int Max=1100;
vector< vector<int> > Bmap;
int n, m, k, nm;
int mark[Max];
bool flag[Max];
bool dfs(int pos)
{
int i, pre, tp;
for(i=0; i < Bmap[pos].size() ;i++) {
tp = Bmap[pos][i];
if( !flag[tp] ) {
flag[tp] = true;
pre = mark[tp];
mark[tp] = pos;
if(pre==-1 || dfs(pre)) return true;
mark[tp] = pre;
}
}
return false;
}
inline int Max_Match()
{
int mmax = 0, i;
for(i=1; i <= m ;i++) {
memset(flag,0,sizeof(flag));
if( dfs(i) ) mmax++;
}
return mmax;
}
int main()
{
int i, j, id, id2;
while(scanf("%d", &k)==1 && k) {
scanf("%d%d",&m, &n);
nm = n + m;
Bmap.clear(); Bmap.resize(nm+10);
memset(mark,-1,sizeof(mark));
for(j=0; j < k ;j++) {
scanf("%d %d", &id, &id2);
id2 += m;
Bmap[id].push_back(id2);
}
printf("%d\n", Max_Match());
}
}