回溯、圖論——最大團問題(求最大完全子圖)

 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.測試結果

 

發佈了68 篇原創文章 · 獲贊 10 · 訪問量 4360
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章