1. 程式人生 > 其它 >圖論演算法

圖論演算法

技術標籤:演算法筆記演算法圖論最小生成樹單源最短路徑拓撲排序

圖論演算法

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());
    }
}