問題描述:團就是最大完全子圖。
給定無向圖G=(V,E)。如果UV,且對任意u,v
U 有(u,v)
E,則稱U 是G 的完全子圖。
G 的完全子圖U是G的團當且僅當U不包含在G 的更大的完全子圖中,即U就是最大完全子圖。
G 的最大團是指G中所含頂點數最多的團。
例如:
(a) (b) (c) (d)
圖a是一個無向圖,圖b、c、d都是圖a的團,且都是最大團。
求最大團的思路:
首先設最大團為一個空團,往其中加入一個頂點,然后依次考慮每個頂點,查看該頂點加入團之后仍然構成一個團,如果可以,考慮將該頂點加入團或者舍棄兩種情況,如果不行,直接舍棄,然后遞歸判斷下一頂點。對於無連接或者直接舍棄兩種情況,在遞歸前,可采用剪枝策略來避免無效搜索。
為了判斷當前頂點加入團之后是否仍是一個團,只需要考慮該頂點和團中頂點是否都有連接。
程序中采用了一個比較簡單的剪枝策略,即如果剩余未考慮的頂點數加上團中頂點數不大於當前解的頂點數,可停止繼續深度搜索,否則繼續深度遞歸
當搜索到一個葉結點時,即可停止搜索,此時更新最優解和最優值。
以下是轉載的某篇論文的,百度文庫的
http://wenku.baidu.com/view/1bd93526a5e9856a561260e2.html
3.6 回溯法
3.6.1 算法基本思想
回溯法(Backtracking Algorithm, BA)有“通用的解題法”之稱,用它可以系統地搜索一個問題的所有解或任一解,是一個既帶有系統性又帶有跳躍性的搜索算法。在包含問題的所有解的解空間樹中,按照深度優先的策略,從根結點出發搜索解空間樹。算法搜索至解空間樹的任一結點時,總是先判斷該結點是否肯定不包含問題的解,如果肯定不包含,則跳過對以該結點為根的子樹的系統搜索,逐層向其祖先結點回溯;否則,進入該子樹,繼續按照深度優先的策略進行搜索。BA在用來求問題的所有解時,要回溯到根,且根結點的所有子樹都已被搜索遍才結束。而BA在用來求問題的任一解時,只要搜索到問題的一個解即可結束。這種以深度優先的方式系統地搜索問題的解的算法稱為回溯法,它適用於解一些組合數較大的問題。
回溯法搜索解空間樹時,根節點首先成為一個活結點,同時也成為當前的擴展節點。在當前擴展節點處,搜索向縱深方向移至一個新節點。這個新節點就成為一個新的活結點,並成為當前擴展節點。如果當前擴展節點不能再向縱深方向移動,則當前的擴展節點就成為死結點。此時,往回回溯至最近的一個活節點處,並使這個活結點成為當前的擴展節點。
回溯法以這種方式遞歸地在解空間中搜索,直至找到所有要求的解或解空間已無活結點為止。
3.6.2 算法設計思想
搜索:回溯法從根結點出發,按深度優先策略遍歷解空間樹,搜索滿足約束條件的解。
剪枝:在搜索至樹中任一結點時,先判斷該結點對應的部分解是否滿足約束條件,或者是否超出目標函數的界;也即判斷該結點是否包含問題的解,如果肯定不包含,則跳過對以該結點為根的子樹的搜索,即剪枝(Pruning);否則,進入以該結點為根的子樹,繼續按照深度優先的策略搜索。
一般來講,回溯法求解問題的基本步驟如下:
(1) 針對所給問題,定義問題的解空間;
(2) 確定易於搜索的解空間結構;
(3) 以深度優先方式搜索解空間,並在搜索過程中利用Pruning函數剪去無效的搜索。
無向圖G的最大團問題可以看作是圖G的頂點集V的子集選取問題。因此可以用子集樹表示問題的解空間。設當前擴展節點Z位於解空間樹的第i層。在進入左子樹前,必須確認從頂點i到已入選的頂點集中每一個頂點都有邊相連。在進入右子樹之前,必須確認還有足夠多的可選擇頂點使得算法有可能在右子樹中找到更大的團。
用鄰接矩陣表示圖G,n為G的頂點數,cn存儲當前團的頂點數,bestn存儲最大團的頂點數。cn+n-i為進入右子樹的上界函數,當cn+n-i<bestn時,不能在右子樹中找到更大的團,利用剪枝函數可將Z的右結點剪去。
3.6.3 實例分析
如圖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}也是其最大團。
代碼:

1 #include <iostream> 2 #include <memory.h> 3 #include <stdio.h> 4 using namespace std; 5 6 const int maxnum=101; 7 bool array[maxnum][maxnum]; 8 bool use[maxnum]; //進入團的標號 9 int cn,bestn,p,e; 10 11 void dfs(int i) 12 { 13 int j; 14 bool flag; 15 16 if(i>p) 17 { 18 bestn=cn; 19 printf("%d\n",bestn); 20 for(j=1;j<=p;j++) 21 if(use[j]) 22 printf("%d ",j); 23 printf("\n"); 24 return ; 25 } 26 27 flag=true; 28 for(j=1;j<i;j++) 29 if(use[j]&&!array[j][i]) 30 { 31 flag=false; 32 break; 33 } 34 if(flag) 35 { 36 cn++; 37 use[i]=true; 38 dfs(i+1); 39 cn--; 40 } 41 if(cn+p-i>bestn) //剪枝 42 { 43 use[i]=false; 44 dfs(i+1); 45 } 46 } 47 48 int main() 49 { 50 int num,i,u,v; 51 scanf("%d",&num); 52 while(num--) 53 { 54 memset(array,false,sizeof(array)); 55 memset(use,false,sizeof(use)); 56 scanf("%d%d",&p,&e); 57 for(i=0;i<e;i++) 58 { 59 scanf("%d%d",&u,&v); 60 array[u][v]=true; 61 array[v][u]=true; 62 } 63 64 cn=bestn=0; 65 dfs(1); 66 //printf("%d\n",bestn); 67 } 68 69 return 0; 70 } 71 72 /* 73 1 74 5 7 75 1 2 76 1 4 77 1 5 78 2 3 79 2 5 80 3 5 81 4 5 82 */