一、問題描述
了解最大團問題(Maximum Clique Problem, MCP)之前需要明白幾個概念。復習一下圖論知識......
完全圖:如果無向圖中的任何一對頂點之間都有一條邊,這種無向圖稱為完全圖。
完全子圖:給定無向圖G=(V,E)。如果UV,且對任意u,v
U 有(u,v)
E,則稱U 是G 的完全子圖。
團(最大完全子圖): U是G的團當且僅當U不包含在G 的更大的完全子圖中
最大團:G 的最大團是指G中所含頂點數最多的團。
空子圖:給定無向圖G=(V,E)。如果UV,且對任意u,v
U 有(u,v) ∉ E,則稱U 是G 的空子圖。G的空子圖U是G的獨立集當且僅當U不包含在G的更大空子圖中。
獨立集:對於給定無向圖G=(V,E)。如果頂點集合V*V,若V*中任何兩個頂點均不相鄰,則稱V*為G的點獨立集,或簡稱獨立集。
最大獨立集:G中所含頂點數最多的獨立集。
例如:
(a) (b) (c) (d)
圖a是一個無向圖,圖b、c、d都是圖a的團,且都是最大團。
補圖:
圖G的補圖,通俗的來講就是完全圖Kn去除G的邊集后得到的圖Kn-G。在圖論里面,一個圖G的補圖(complement)或者反面(inverse)是一個圖有着跟G相同的點,而且這些點之間有邊相連當且僅當在G里面他們沒有邊相連。
二、算法設計
大致思路:
首先設最大團為一個空團,往其中加入一個頂點,然后依次考慮每個頂點,查看該頂點加入團之后仍然構成一個團,如果可以,考慮將該頂點加入團或者舍棄兩種情況,如果不行,直接舍棄,然后遞歸判斷下一頂點。對於無連接或者直接舍棄兩種情況,在遞歸前,可采用剪枝策略來避免無效搜索。
為了判斷當前頂點加入團之后是否仍是一個團,只需要考慮該頂點和團中頂點是否都有連接。
程序中采用了一個比較簡單的剪枝策略,即如果剩余未考慮的頂點數加上團中頂點數不大於當前解的頂點數,可停止繼續深度搜索,否則繼續深度遞歸。
三、實例分析
如圖1所示,給定無向圖G={V, E},其中V ={1,2,3,4,5},E={(1,2), (1,4), (1,5), (2,3), (2,5), (3,5), (4,5)}。根據MCP定義,子集{1,2}是圖G的一個大小為2的完全子圖,但不是一個團,因為它包含於G的更大的完全子圖{1,2,5}之中。{1,2,5}是G的一個最大團。{1,4,5}和{2,3,5}也是G的最大團。
圖2是無向圖G的補圖G'。根據最大獨立集定義,{2,4}是G的一個空子圖,同時也是G的一個最大獨立集。雖然{1,2}也是G'的空子圖,但它不是G'的獨立集,因為它包含在G'的空子圖{1,2,5}中。{1,2,5}是G'的最大獨立集。{1,4,5}和{2,3,5}也是G'的最大獨立集。
以圖1為例,利用回溯法搜索其空間樹,具體搜索過程(見圖3所示)如下:假設我們按照1®2®3®4®5的順序深度搜索。
開始時,根結點R是唯一活結點,也是當前擴展結點,位於第1層,此時當前團的頂點數cn=0,最大團的頂點數bestn=0。
在這個擴展結點處,我們假定R和第二層的頂點1之間有邊相連,則沿縱深方向移至頂點1處。此時結點R和頂點1都是活結點,頂點1成為當前的擴展結點。此時當前團的頂點數cn=1,最大團的頂點數bestn=0。繼續深度搜索至第3層頂點2處,此時頂點1和2有邊相連,都是活結點,頂點2成為當前擴展結點。
此時當前團的頂點數cn=2,最大團的頂點數bestn=0。再深度搜索至第4層頂點3處,由於頂點3和2有邊相連但與頂點1無邊相連,則利用剪枝函數剪去該枝,此時由於cn+n-i=2+5-4=3>bestn=0,則回溯到結點2處進入右子樹,開始搜索。此時當前團的頂點數cn=2,最大團的頂點數bestn=0。再深度搜索至第5層頂點4處,由於頂點3和4無邊相連,剪去該枝,回溯到結點3處進入右子樹,此時當前團的頂點數cn=2,最大團的頂點數bestn=0。
繼續深度搜索至第6層頂點5處,由於頂點5和4有邊相連,且與頂點1和2都有邊相連,則進入左子樹搜索。由於結點5是一個葉結點,故我們得到一個可行解,此時當前團的頂點數cn=3,最大團的頂點數bestn=3。vi的取值由頂點1至頂點5所唯一確定,即v=(1, 2, 5)。此時頂點5已不能再縱深擴展,成為死結點,我們返回到結點4處。由於此時cn+n-i=3+5-6=2<bestn=3,不能在右子樹中找到更大的團,利用剪枝函數可將結點4的右結點剪去。以此回溯,直至根結點R再次成為當前的擴展結點,沿着右子樹的縱深方向移動,直至遍歷整個解空間。最后得到圖1的按照1®2®3®4®5的順序深度搜索的最大團為U={1,2,5}。當然{1,4,5}和{2,3,5}也是其最大團。
四、代碼描述
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int maxnum=101; bool a[maxnum][maxnum];//圖的鄰接矩陣 bool x[maxnum]; //當前解 int cn;//當前團的頂點數 int bestn;//當前的最優解 int n;//圖G的頂點數 int e;//圖G的邊數 void backtrack(int i) { int j; if(i>n) { bestn=cn; printf("%d\n",bestn); for(j=1; j<=n; j++) { if(x[j]) { printf("%d ",j); } } printf("\n"); return ; } bool ok=true; for(j=1; j<i; j++) { if(x[j]&&!a[j][i])//i與j不相連 { ok=false; break; } } if(ok)//進入左子樹 { cn++; x[i]=true; backtrack(i+1); cn--; } if(cn+n-i>bestn) //剪枝 { x[i]=false; backtrack(i+1); } } int main() { int i,u,v; memset(a,false,sizeof(a)); memset(x,false,sizeof(x)); scanf("%d%d",&n,&e); for(i=0; i<e; i++) { scanf("%d%d",&u,&v); a[u][v]=true; a[v][u]=true; } cn=bestn=0; backtrack(1); return 0; } /* 5 7 1 2 1 4 1 5 2 5 2 3 3 5 4 5 */