1. 程式人生 > >第五章 (回溯法)最大團問題

第五章 (回溯法)最大團問題

原文:http://www.cnblogs.com/pushing-my-way/archive/2012/08/08/2627993.html

問題描述:團就是最大完全子圖。

給定無向圖G=(V,E)。如果UV,且對任意u,vU 有(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到已入選的頂點集中每一個頂點都有邊相連。在進入右子樹之前,必須確認還有足夠多的可選擇頂點使得演算法有可能在右子樹中找到更大的團。

用鄰接矩陣表示圖GnG的頂點數,cn儲存當前團的頂點數,bestn儲存最大團的頂點數。cn+n-i為進入右子樹的上界函式,當cn+n-i<bestn時,不能在右子樹中找到更大的團,利用剪枝函式可將Z的右結點剪去。

3.6.3 例項分析

如圖1所示,給定無向圖G={VE},其中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 */
複製程式碼