1.問題分析
要想解決最大團問題,也就是求最大完全子圖。我們需要了解相關概念,現在有如下圖:
(1)完全子圖:
給定無向圖G=(V,E),其中V是頂點集,E是邊集。G'=(V',E')如果頂點集V'∈V,E'∈E,且G'種任意兩個點有邊相連,則稱G'是G的完全子圖。例如下面的幾個圖都是上圖的完全子圖:
(2)團:
G的完全子圖是G的團,當且僅當G'不包含在G的更大的完全子圖中,也就是說G'是G的極大完全子圖。比如圖中(c),(d)是G的團,而(a)(b)不是G的團,因爲他們包含在G的更大的完全子圖(c)中。
(3)最大團:
G的最大團是指G的所有團中,含頂點數最大的團,比如說(d)就是最大團。
2.算法設計
(1)約束條件:
最大團問題的解空間包含2^n個子集,這些子集存在集合中的某兩個結點沒邊相連的情況。顯然這種情況的可能姐不是問題的可行解,故需要設置約束條件來判斷是否有情況可能導致問題的可行解。
假設現在擴展結點到t層,那麼1~t-1個結點狀態(是否已經在團裏)已經確定。接下來沿着擴展點左分支進行擴展,此時需要判斷是否將 t結點 放進團裏。則判斷 t結點 和1~t-1 個結點中被選中的結點是否有相連,相連就放進團裏,x[t]=1,否則不放進團裏,x[t]=0.如圖所示:
(2)限界條件(剪枝函數):
假設當前的擴展結點爲z,如果z處於第t層,前面1~t-1 層的結點的狀態已經確定了。接下來確定t結點的狀態。前t個結點狀態確定當前已放團內的結點個數(用cn表示),假設t+1 ~ n 的所有結點都放進團,用fn表示,且fn=n-t ,則cn+fn是所有從根出發的路徑中經過中間結點z的可行解所包含結點個數的上界。
如果fn+cn小於等於當前最優解bestn,則說明不再需要從中間結點z繼續像子孫結點搜索。因此,限界條件可以描述爲:cn+fn>bestn。
(3)搜索過程:
從根節點開始,以深度遊戲按的方式進行。每次搜索到一個結點,判斷約束條件,看是否可以將當前結點加入到團中。如果可以,則沿着當前結點左分支繼續向下搜索,如果不可以,判斷戒指函數,如果滿足則沿着當前結點的右分支繼續向下搜索。
3.源代碼
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> using namespace std; const int N=111; //設置矩陣的大小 int graph[N][N]; //用鄰接矩陣存儲圖 bool x[N]; //是否將i個頂點加入團中 bool bestx[N]; //記錄最優已經放入團中的頂點 int bestn; //記錄最優值 int cn; //已經放入團中的結點數 int n;//頂點個數 int m; //邊的個數 bool place(int t) //判斷是否能放進團中 { bool OK =true; for (int j=1;j<t;j++) //判斷目前擴展的t頂點和前面t-1個頂點是否相連。 { if(x[j] && graph[t][j]==0) //如果不相連 { OK=false; //返回false break; } } return OK; //如果相連。返回true } void backtrack(int t) //回溯,遞推 { if(t>n) //當到達葉子結點 { for(int i=1;i<=n;i++) { bestx[i]=x[i]; //記錄最優值結點號 } bestn=cn; //記錄最優值 return; } if(place(t)) //如果能放進團中 { x[t]=1;//標爲1 cn++; //個數+1 backtrack(t+1); //向下遞推 cn--; //向上回溯 } if(cn+n-t>bestn) //限界條件,進入右子樹,不能加入團中。 { x[t]=0; //不能放入團中,標爲0 backtrack(t+1); //向下遞推。 } } int main() { int u; //結點1 int v; //結點2 cout << "請輸入結點的個數n;"<< endl; cin >> n; cout << "請輸入邊數m:"<< endl; cin >>m; memset(graph,0,sizeof(graph)); cout <<"請輸入有邊相連的兩個頂點u和v:"<< endl; for (int i=1;i<=m;i++) { cin >> u>> v; graph[u][v]=1; graph[v][u]=1; } bestn=0; cn=0; backtrack(1); cout << "最大團的結點個數有:"<< bestn << endl; cout << "結點有:"<< endl; for (int i=1;i<=n;i++) { if(bestx[i]) { cout << i << " "; //輸出的是結點號 } } return 0; }
4.測試結果