五一時候隨便翻書看到了一些關於離散數學圖論的模板和算法,大概總結了一下,圖論要比數論稍簡單一點點。。。ios
1、
點用邊連起來就叫作圖,嚴格意義上講,圖是一種數據結構,定義爲:graph=(V,E)。V是一個非空有限集合,表明頂點(結點),E表明邊的集合。
2、圖的一些定義和概念
(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。ui
若是一個圖存在一筆畫,則一筆畫的路徑叫作歐拉路,若是最後又回到起點,那這個路徑叫作歐拉回路。
定理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
這種條件在紙上畫出大概的一個圖就能發現點和線之間的關聯。
這也是歐拉回路一個模板spa
#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; }
歐拉回路是指不重複地走過全部路徑的迴路,而哈密爾頓環是指不重複地走過全部的點,而且最後還能回到起點的迴路。code
#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;
}
以下圖所示,咱們把邊帶有權值的圖稱爲帶權圖。邊的權值能夠理解爲兩點之間的距離。一張圖中任意兩點間會有不一樣的路徑相連。最短路徑就是指鏈接兩點的這些路徑中最短的一條。
blog
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的最短路徑。ci
主體代碼是很好理解的,遍歷全部點,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; }