圖論算法


五一時候隨便翻書看到了一些關於離散數學圖論的模板和算法,大概總結了一下,圖論要比數論稍簡單一點點。。。

一、
  點用邊連起來就叫做圖,嚴格意義上講,圖是一種數據結構,定義為:graph=(V,E)。V是一個非空有限集合,代表頂點(結點),E代表邊的集合。
二、圖的一些定義和概念
(a)有向圖:圖的邊有方向,只能按箭頭方向從一點到另一點。(a)就是一個有向圖。
(b)無向圖:圖的邊沒有方向,可以雙向。(b)就是一個無向圖。
結點的度:無向圖中與結點相連的邊的數目,稱為結點的度。
結點的入度:在有向圖中,以這個結點為終點的有向邊的數目。結點的出度:在有向圖中,以這個結點為起點的有向邊的數目。   

 

                              

權值:邊的“費用”,可以形象地理解為邊的長度。
連通:如果圖中結點U,V之間存在一條從U通過若干條邊、點到達V的通路,則稱U、V 是連通的。
回路:起點和終點相同的路徑,稱為回路,或“環”。
完全圖:一個n 階的完全無向圖含有n*(n-1)/2 條邊;一個n 階的完全有向圖含有n*(n-1)條邊;

    稠密圖:一個邊數接近完全圖的圖。
    稀疏圖:一個邊數遠遠少於完全圖的圖。
    
強連通分量:有向圖中任意兩點都連通的最大子圖。右圖中,1-2-5構成一個強連通分量。特殊地,單個點也算一個強連通分量,所以右圖有三個強連通分量:1-2-5,4,3。

  • 圖的存儲結構

1.二維數組鄰接矩陣存儲
定義int  G[101][101];
G[i][j]的值,表示從點i到點j的邊的權值,定義如下:

                0  1  1  1                     0  1  1                    
G(A)=   1  0  1  1    G(B)=    0  0  1                    
            1  1  0  0                     0  1  0  
            1  1  0  0        

 

 

 

  • 圖的遍歷
void dfs(int i)
{
    visited[i] = true;
    for(int j = 1;j <= num[i] ; j++)
    if(!visited[g[i][j]])
    dfs(g[i][j]);
}
int main()
{
    memset(visited,false,sizeof(visited));
    for(int i=1 ; i<=n ;i++)
    
    if(!visited[i])
    dfs(i);
    
}

 

可以看到上面這段遍歷整張圖的代碼中主函數部分,先把圖中各點初始化false,每次遍歷時先判斷兩點是否聯通將遍歷過的點修改為true。

  • 歐拉回路

如果一個圖存在一筆畫,則一筆畫的路徑叫做歐拉路,如果最后又回到起點,那這個路徑叫做歐拉回路。
定理1:存在歐拉路的條件:圖是連通的,有且只有2個奇點。
定理2:存在歐拉回路的條件:圖是連通的,有0個奇點。
根據一筆畫的兩個定理,如果尋找歐拉回路,對任意一個點執行深度優先遍歷;找歐拉路,則對一個奇點執行DFS,時間復雜度為O(m+n),m為邊數,n是點數。

樣例輸入:第一行n,m,有n個點,m條邊,以下m行描述每條邊連接的兩點。
    5 5
    1 2
    2 3
    3 4
    4 5
    5 1
樣例輸出:歐拉路或歐拉回路
    1 5 4 3 2 1

這種條件在紙上畫出大概的一個圖就能發現點和線之間的關聯。
這也是歐拉回路一個模板

#include<iostream>
using namespace std;
#define maxn 100
int g[maxn][maxn];               //儲存矩陣
int du[maxn];                    //記錄一個點連了幾條邊
int circuit[maxn];               //記錄歐拉回路
int n,e,circuitpos,i,j,x,y,start;
void find_circuit(int i)         //記錄其中一個點的歐拉回路
{
    int j;
    for(j=1;j<=n;j++)
    if(g[i][j]==1)
    {
        g[i][j] = g[j][i] = 0;           //記錄兩個聯通點后把他們的之間路徑清空
        find_circuit(j);
    }
    circuit[++circuitpos] = i;
}
int main()
{
    memset(g,0,sizeif(g));
    cin>>n>>e;
    for (int i=1 ;i<=e ;i++)
    {
        cin>>x>>y;
        g[x][y] = g[y][x] =1;
        du[x]++;
        du[y]++;
    }
    start = 1;
    for(i =1 ; i<=n ;i++)
    if(du[i]%2==1)                      //偶數點無法判斷此路徑是否被記錄,因此從奇數點
開始找,找到奇數點后代入函數做記錄。 start
= i; circuitpos = 0; find_circuit(start); for(i=1;i<=circuitpos;i++) cout<<circuit[i]<<endl; }

 

  • 哈密爾頓環

  歐拉回路是指不重復地走過所有路徑的回路,而哈密爾頓環是指不重復地走過所有的點,並且最后還能回到起點的回路。

#include<iostream>
#include<cstring>
using namespace std;
int start,length,x,n; 
bool visited[101],v1[101];
int ans[101], num[101];
int g[101][101];
void print()
{    int i;
     for (i = 1; i <= length; i++)
         cout << ' ' << ans[i];
     cout << endl;    
}
void dfs(int last,int i)                      //圖用數組模擬鄰接表存儲,訪問點i,last表示上次訪問的點
{
    visited[i] = true;                        //標記為已經訪問過
    v1[i] = true;                             //標記為已在一張圖中出現過
    ans[++length] = i;                                     
    for (int j = 1; j <= num[i]; j++)  
  { 
    if (g[i][j]==x&&g[i][j]!=last)          //回到起點,構成哈密爾頓環
          {
          ans[++length] = g[i][j];  
          print();                          
          length--;
          break;
          } 
     if (!visited[g[i][j]]) dfs(i,g[i][j]);   //遍歷與i相關聯的所有未訪問過的頂點
    }
    length--;
    visited[i] = false;                       
} 
int main()
{
    memset(visited,false,sizeof(visited));
    memset(v1,false,sizeof(v1));
    for (x = 1; x <= n; x++)
       //每一個點都作為起點嘗試訪問,因為不是從任何一點開始都能找過整個圖的
       if (!v1[x])              //如果點x不在之前曾經被訪問過的圖里。
        {
            length = 0;        
            dfs(x);
        }
    return 0;
}
  • 最短路徑

 如下圖所示,我們把邊帶有權值的圖稱為帶權圖。邊的權值可以理解為兩點之間的距離。一張圖中任意兩點間會有不同的路徑相連。最短路徑就是指連接兩點的這些路徑中最短的一條。

 

 1.弗洛依德l算法 O(N3)
  簡稱Floyed(弗洛伊德)算法,是最簡單的最短路徑算法,可以計算圖中任意兩點間的最短路徑。Floyed的時間復雜度是O (N3),適用於出現負邊權的情況。
算法描述:
       初始化:點u、v如果有邊相連,則dis[u][v]=w[u][v]。
  如果不相連則dis[u][v]=0x7fffffff
For (k = 1; k <= n; k++)
    For (i = 1; i <= n; i++)
     For (j = 1; j <= n; j++)
         If (dis[i][j] >dis[i][k] + dis[k][j])
             dis[i][j] = dis[i][k] + dis[k][j];
        算法結束:dis[i][j]得出的就是從i到j的最短路徑。

主體代碼是很好理解的,遍歷所有點,i,j兩點間的距離比i到k,k到j的距離長,就更新i j兩點間的距離。

 

最短路徑問題
【問題描述】
  平面上有n個點(n<=100),每個點的坐標均在-10000~10000之間。其中的一些點之間有連線。
  若有連線,則表示可從一個點到達另一個點,即兩點間有通路,通路的距離為兩點間的直線距離。現在的
  任務是找出從一點到另一點之間的最短路徑。
【輸入格式】
  輸入文件為short.in,共n+m+3行,其中:
  第一行為整數n。
  第2行到第n+1行(共n行) ,每行兩個整數x和y,描述了一個點的坐標。
  第n+2行為一個整數m,表示圖中連線的個數。
  此后的m 行,每行描述一條連線,由兩個整數i和j組成,表示第i個點和第j個點之間有連線。
  最后一行:兩個整數s和t,分別表示源點和目標點。
【輸出格式】
  輸出文件為short.out,僅一行,一個實數(保留兩位小數),表示從s到t的最短路徑長度

 

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
int a[101][3];
double f[101][101];
int n,i,j,k,x,y,m,s,e;
int main()
{
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i][1] >> a[i][2];
    cin >> m;
    memset(f,0x7f,sizeof(f));                    //初始化f數組為最大值
    for (i = 1; i <= m; i++)                           //預處理出x、y間距離
    {
      cin >> x >> y;
      f[y][x] = f[x][y] = sqrt(pow(double(a[x][1]-a[y][1]),2)+pow(double(a[x][2]-a[y][2]),2));
                      //求(x1,y1),(x2,y2)距離 
    }
    cin >> s >> e;
    for (k = 1; k <= n; k++)                     //floyed 最短路算法
       for (i = 1; i <= n; i++)
          for (j = 1; j <= n; j++)
             if ((i != j) && (i != k) && (j != k) && (f[i][k]+f[k][j] < f[i][j]))
                 f[i][j] = f[i][k] + f[k][j];
    printf("%.2lf\n",f[s][e]);
    return 0;
}

2.Dijkstra算法O (N2)
用來計算從一個點到其他所有點的最短路徑的算法,是一種單源最短路徑算法。也就是說,只能計算起點只有一個的情況。
Dijkstra的時間復雜度是O (N2),它不能處理存在負邊權的情況。dijkstra算法優點在於時間復雜度比弗洛伊德低一個量級,但是不能處理負權值。
算法描述:
       設起點為s,dis[v]表示從s到v的最短路徑,pre[v]為v的前驅節點,用來輸出路徑。
       a)初始化:dis[v]=∞(v≠s); dis[s]=0; pre[s]=0;
       b)For (i = 1; i <= n ; i++)
            1.在沒有被訪問過的點中找一個頂點u使得dis[u]是最小的。
            2.u標記為已確定最短路徑
            3.For 與u相連的每個未確定最短路徑的頂點v
              if  (dis[u]+w[u][v] < dis[v])
               {
                  dis[v] = dis[u] + w[u][v];
                  pre[v] = u;
               }
        c)算法結束:dis[v]為s到v的最短距離;pre[v]為v的前驅節點,用來輸出路徑。

    從起點到一個點的最短路徑一定會經過至少一個“中轉點”(例如下圖1到5的最短路徑,中轉點是2。特殊地,我們認為起點1也是一個“中轉點”)。顯而易見,如果我們想求出起點到一個點的最短路徑,那我們必然要先求出中轉點的最短路徑(例如我們必須先求出點2 的最短路徑后,才能求出從起點到5的最短路徑)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int a[101][3];
double c[101];
bool b[101];
double f[101][101];
int n,i,j,k,x,y,m,s,e;
double minl;
double maxx = 1e30;
int main()
{
    cin >> n;
    for (i = 1; i <= n; i++)
      cin >> a[i][1] >> a[i][2];
    for (i = 1; i <= n; i++)
      for(j = 1; j <= n; j++)
        f[i][j] = maxx;                         //f數組初始化最大值
    cin >> m;
    for (i = 1; i <= m; i++)                    //預處理x.y間距離f[x][y]
    {
        cin >> x >> y;
        f[x][y] = f[y][x] = sqrt(pow(double(a[x][1]-a[y][1]),2)+pow(double(a[x][2]-a[y][2]),2));
    }
   cin >> s >> e;
    for (i = 1; i <= n; i++) 
      c[i] = f[s][i];
    memset(b,false,sizeof(b));      //dijkstra 最短路
    b[s] = true;
    c[s] = 0; 
    for (i = 1; i <= n-1; i++)
    {
        minl = maxx;
        k = 0;
        for (j = 1; j <= n; j++)     //查找可以更新的點
           if ((! b[j]) && (c[j] < minl))
            {
                minl = c[j];
                k = j;
            }
        if (k == 0) break;
        b[k] = true;
        for (j = 1; j <= n; j++)
           if (c[k] + f[k][j] < c[j]) 
             c[j] = c[k] + f[k][j];
    }    
   printf("%.2lf\n",c[e]);
   return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM