極大團(tuán)(maximal clique)算法:Bron-Kerbosch算法

不了解極大團(tuán)(maximal clique)的瞎访,請(qǐng)看極大團(tuán)這篇文章。
該算法是由Coen Bron和Joep Kerbosch在1973提出的,論文原文
參考資料:
Bron-Kerbosch算法視頻介紹
極大團(tuán)算法
當(dāng)給出一個(gè)圖之后葫督,我們應(yīng)該怎么去找出其中的極大團(tuán)呢?


尋找極大團(tuán)的簡單思想就是:
1恋沃、生成原始圖的所有子圖(可能有2n-1個(gè)子圖,n表示結(jié)點(diǎn)個(gè)數(shù))必指;
2囊咏、判斷這些子圖是不是團(tuán);
3塔橡、將不是極大團(tuán)的團(tuán)都刪除梅割;
在我們上面給出的圖中,會(huì)有許多的子圖葛家,例如:
子圖

這里沒有列出所有的子圖户辞,只有部分。
在這些子圖里面癞谒,我們會(huì)發(fā)現(xiàn)有些并不是團(tuán)底燎,例如{1、2弹砚、3}組成的圖双仍,因?yàn)?和3之間并不相連,所以這樣的圖不符合要求桌吃,將其去掉朱沃。
團(tuán)

剩下的所有子圖,都是滿足了團(tuán)的要求的圖。最后逗物,其中還有許多搬卒,不是極大團(tuán)的,例如{0翎卓、1}就不是極大團(tuán)契邀,因?yàn)橛衶0、1莲祸、2}的存在蹂安。這些不是極大團(tuán)的團(tuán),我們也要去掉锐帜。
極大團(tuán)

現(xiàn)在剩下的就是我們要找的極大團(tuán)。

Bron-Kerbosch算法(Version 1)

這個(gè)算法主要是構(gòu)造了三個(gè)集合畜号,我們假設(shè):
R集合記錄的是當(dāng)前極大團(tuán)中已經(jīng)加入的點(diǎn)缴阎。
P集合記錄的是可能還能加入的點(diǎn)(也就是與R集合中所有點(diǎn)都有邊存在的點(diǎn),這樣加入后简软,才會(huì)構(gòu)成團(tuán))
X集合記錄的是已經(jīng)加入過某個(gè)極大團(tuán)的點(diǎn)(作用是判重蛮拔,因?yàn)闀?huì)從每個(gè)結(jié)點(diǎn)開始,枚舉所有的團(tuán)痹升,如果不對(duì)已經(jīng)加入某個(gè)極大團(tuán)的點(diǎn)建炫,進(jìn)行標(biāo)記,可能會(huì)有重復(fù)的極大團(tuán)出現(xiàn))
偽代碼如下:

Bron-Kerbosch Algorithm(Version 1)
R={}   //已確定的極大團(tuán)頂點(diǎn)的集合
P={v}  //未處理頂點(diǎn)集疼蛾,初始狀態(tài)是所有結(jié)點(diǎn)
X={}   //已搜過的并且屬于某個(gè)極大團(tuán)的頂點(diǎn)集合

BronKerbosch(R, P, X):
   if P and X are both empty:
       report R as a maximal clique
   for each vertex v in P:
       BronKerbosch1(R ? {v}, P ? N(v), X ? N(v))
       P := P \ {v}
       X := X ? {v}

基礎(chǔ)的Born_Kerbosch算法:
1肛跌、對(duì)于每一個(gè)在集合P中的點(diǎn)v,我們把v加入集合R(集合P中的點(diǎn)與集合R中所有的點(diǎn)都是連接的察郁,這樣加入v衍慎,保證集合R依然是一個(gè)團(tuán)),對(duì)在P集合中且與點(diǎn)v相連的這部分集合中尋找下一個(gè)可能加入R集合的點(diǎn)(意思就是更新P集合皮钠,使P集合中的點(diǎn)依舊可以和R集合中的點(diǎn)相連接稳捆,因?yàn)檫@里新R集合新加入了v,所以只要是原P集合中且與v相連的麦轰,那這些結(jié)點(diǎn)就是與新的R集合中所有結(jié)點(diǎn)都相連)乔夯。
2、回溯時(shí)我們把v從P集合中移出款侵,加入X集合代表當(dāng)前狀態(tài)下對(duì)包含點(diǎn)v的極大團(tuán)已經(jīng)計(jì)算完畢了末荐。
3、R集合為極大團(tuán)的時(shí)候喳坠,必須要滿足P與X都是空的鞠评。P存放的是還可能加入R集合的點(diǎn),P集合為空代表沒有點(diǎn)還能再加入到R集合當(dāng)中壕鹉,而X集合存放的是已經(jīng)完成極大團(tuán)計(jì)數(shù)的點(diǎn)剃幌,而且X集合中的點(diǎn)必然是與所有R集合中的點(diǎn)都有邊存在的(因?yàn)槲覀兠看蜗蛳逻M(jìn)行dfs的時(shí)候聋涨,還對(duì)P集合和X集合分別進(jìn)行取與R集合內(nèi)都相鄰的操作來保證,而且加入X集合中的點(diǎn)负乡,就是從R集合中取出來的牍白,當(dāng)然會(huì)和R集合中的所有結(jié)點(diǎn)都有邊),也即X集合中點(diǎn)必然可以與R集合構(gòu)成極大團(tuán)抖棘。如果X集合不是空的的話茂腥,可以把X集合中的點(diǎn)加入R集團(tuán),此時(shí)R集團(tuán)依然是團(tuán)切省,結(jié)點(diǎn)數(shù)比剛才增加了最岗,說明剛才的R集合就不是極大團(tuán),那么說明R集合中的極大團(tuán)是在之前計(jì)算包含X集合中的點(diǎn)的極大團(tuán)的時(shí)候已經(jīng)計(jì)算過了的朝捆,故當(dāng)且僅當(dāng)P般渡、X都為空集合的時(shí)候R才是一個(gè)極大團(tuán)

算法的實(shí)例:


Bron-Kerbosch Algorithm(Version 1)

在最開始中芙盘,集合P會(huì)初始化為所有的結(jié)點(diǎn)驯用,其他集合為空集。


Bron-Kerbosch Algorithm(Version 1)

首先儒老,我們將集合P中的第一個(gè)結(jié)點(diǎn)蝴乔,1號(hào)結(jié)點(diǎn),放入到R中驮樊。這個(gè)時(shí)候薇正,我們的遍歷要進(jìn)入下一層了,所以要更新集合P和集合X巩剖,這里集合X沒有變化铝穷,依然是空集。集合P中的點(diǎn)要是和集合R中的所有結(jié)點(diǎn)都是連接的佳魔,顯然我們只需要找到曙聂,原來的P集合中是1號(hào)集合的鄰居結(jié)點(diǎn)的那些點(diǎn)就行了,這里就是{2鞠鲜、3}宁脊,保證了P集合中的每個(gè)結(jié)點(diǎn)都可以和R集合中的結(jié)點(diǎn)想連接,其實(shí)X集合我們也會(huì)執(zhí)行同樣的操作贤姆,以保證X集合中的結(jié)點(diǎn)都是和R集合中的所有結(jié)點(diǎn)連接的榆苞。
Bron-Kerbosch Algorithm(Version 1)

同樣的,我們繼續(xù)將P集合中的結(jié)點(diǎn)放入R集合霞捡,這次放入2號(hào)結(jié)點(diǎn)坐漏,R集合變?yōu)閧1、2},P集合取原P集合中且和2號(hào)結(jié)點(diǎn)相連接的結(jié)點(diǎn)赊琳,P集合就變?yōu)閧3}街夭。


Bron-Kerbosch Algorithm(Version 1)

最后同樣的我們將P集合中的結(jié)點(diǎn),放入R集合躏筏。此時(shí)P集合X集合都為空集板丽,說明這個(gè)團(tuán)已經(jīng)不能再擴(kuò)充了,所以{1趁尼、2埃碱、3}就是一個(gè)極大團(tuán)。
Bron-Kerbosch Algorithm(Version 1)

??我們從1號(hào)結(jié)點(diǎn)開始酥泞,先遍歷了{(lán)1砚殿、2},所以還要從1開始遍歷{1芝囤、3}瓮具。按照DFS的規(guī)則,會(huì)先退回到上一層凡人,也就是R={1、2}叹阔,P={3}這一層挠轴,我們會(huì)將v結(jié)點(diǎn),也就是這一層里操作的結(jié)點(diǎn)耳幢,在這里是3號(hào)結(jié)點(diǎn)放入X集合中岸晦,表示它已經(jīng)參與了極大團(tuán)的構(gòu)。在這一層里最后就變?yōu)榱薘={1睛藻、2}启上,P={},X={3}店印,因?yàn)閄不為空冈在,且P為空,所以R={1按摘、2}不是一個(gè)極大團(tuán)包券,然后我們要回溯到上一層了,也就是R={1}炫贤、P={2溅固、3}、X={}這一層兰珍,再將此時(shí)的操作結(jié)點(diǎn)2號(hào)結(jié)點(diǎn)侍郭,放入X中,表示它已經(jīng)屬于某個(gè)極大子圖,這時(shí)的三個(gè)集合變?yōu)榱薘={1},P={3},X={2}亮元。

??只要P集合中猛计,還有元素,我們就會(huì)一直執(zhí)行將P集合中的元素加入R集合中的操作苹粟,所以這里將3號(hào)結(jié)點(diǎn)加入有滑,并且更新了P集合和X集合(就是保證P,X中的結(jié)點(diǎn)和R集合中的所有結(jié)點(diǎn)都連接)進(jìn)入下一層,也就是圖中所示的樣子嵌削。

??在這里X={2}毛好,不為空,所以{1苛秕、3}不是極大團(tuán)肌访,因?yàn)閄集合中的在加入R集合后,都是會(huì)讓R集合滿足極大團(tuán)的艇劫,顯然{1吼驶、2、3}比{1店煞、3}要大蟹演,所以只要X不為空,R就不會(huì)是極大團(tuán)顷蟀。


Bron-Kerbosch Algorithm(Version 1)

??在完成判斷后酒请,退回到上一層,也就是圖中的R={1}鸣个,P={2羞反、3},X={}的這一層囤萤,不過剛才有說過昼窗,這一層其實(shí)在經(jīng)過將P中的2號(hào)結(jié)點(diǎn)加入到R后,以及后續(xù)的程序涛舍,已經(jīng)變成了R={1},P={3},X={2},此時(shí)我們將正在操作的結(jié)點(diǎn)3號(hào)結(jié)點(diǎn)放入到X中澄惊,變?yōu)镽={1},P={}做盅,X={2缤削、3},此時(shí)P中無結(jié)點(diǎn)吹榴,X中有兩個(gè)結(jié)點(diǎn)亭敢。說明以1開始的遍歷操作以及全部完成了,我們需要再去尋找其他結(jié)點(diǎn)開始的遍歷图筹。
??退回到上一層帅刀,也就是最開始的那一層让腹,即R={}、P={1扣溺、2、3锥余、4}、X={}的這一層嘲恍。把操作的結(jié)點(diǎn),也就是1號(hào)結(jié)點(diǎn)佃牛,放入X中医舆,表示已經(jīng)對(duì)其進(jìn)行過查詢。
??再將P中的下一個(gè)結(jié)點(diǎn)蔬将,也就是2號(hào)結(jié)點(diǎn)爷速,放入R中,同樣的我們要更新P和X霞怀,保證P和X中的結(jié)點(diǎn)遍希,和R中的所有結(jié)點(diǎn),都是連接的里烦,進(jìn)入下一層,得到的如圖所示禁谦,R={2}胁黑、P={3、4}州泊、X={1}丧蘸,表示我們要由2號(hào)結(jié)點(diǎn)開始,尋找極大團(tuán)了遥皂。


Bron-Kerbosch Algorithm(Version 1)

??同樣的力喷,我們將P中的3號(hào)結(jié)點(diǎn)放入R中,同時(shí)更新P和X演训,因?yàn)镻中的4號(hào)結(jié)點(diǎn)弟孟,不與3號(hào)結(jié)點(diǎn)相連,所以P集合變?yōu)榭占颍琗依然為{1}拂募。此時(shí)我們發(fā)現(xiàn)P為空庭猩,但是X={1},所以R={2陈症、3}并不是一個(gè)極大團(tuán)蔼水。這說明由2開始,再走3號(hào)結(jié)點(diǎn)這條路是行不通的录肯,所以需要退回到上一層趴腋,也就是R={2}、P={3论咏、4}、X={1}這一層潘靖,再將3號(hào)結(jié)點(diǎn)放入X中糊余,表示已經(jīng)搜過了贬芥,此時(shí)變?yōu)镽={2}蘸劈,P={4}威沫,X={1棒掠、3}烟很。下面就要走由2號(hào)結(jié)點(diǎn)開始雾袱,在到4號(hào)結(jié)點(diǎn)的這條路了芹橡。
Bron-Kerbosch Algorithm(Version 1)

將P中的4號(hào)結(jié)點(diǎn)放入R中粘驰,同時(shí)更新P和X蝌数,因?yàn)?和3都不與4相連顶伞,所以X集合變?yōu)榭占裘玻藭r(shí)P集合也為空集锨咙,所以R={2酪刀、4}是一個(gè)極大團(tuán)骂倘。


Bron-Kerbosch Algorithm(Version 1)

我們的遍歷當(dāng)然還要繼續(xù)历涝,我們現(xiàn)在找了從最初的圖中荧库,由1開始电爹,已經(jīng)由2開始來尋找極大團(tuán)摇邦,當(dāng)然還要從3開始以及從4開始,尋找極大團(tuán)居扒,結(jié)果如圖所示喜喂。由3號(hào)結(jié)點(diǎn)開始玉吁,R={3}进副、P={}给赞、X={1矫户、2}柑蛇;由4號(hào)結(jié)點(diǎn)開始膳汪,R={4}粘我、P={}征字、X={2}匙姜。X集合都不為空氮昧,所以R集合不是極大團(tuán)袖肥。
Bron-Kerbosch Algorithm(Version 1)

每一次遍歷中椎组,所生成的集合如上圖的右下角所示寸癌,可以很明顯的看出兩個(gè)極大團(tuán)蒸苇。
Bron-Kerbosch Algorithm(Version 1)完整的代碼為:
int some[maxn][maxn];  
//some表示P集合填渠,第一個(gè)[maxn]表示所在的深度氛什,后一個(gè)就是P集合中的某個(gè)結(jié)點(diǎn)的位置
int none[maxn][maxn]; //表示X集合捺檬,其他和some同理
int all[maxn][maxn];    //表示R集合堡纬,其他同理
int mp[maxn][manx];    
void dfs(int d, int an, int sn, int nn)
//d為搜索深度烤镐,an炮叶、sn、nn分別為all(R)侣肄、some(P)稼锅、none(X)集合中頂點(diǎn)數(shù)矩距,
{
    if(!sn && !nn) ++ S;  //sn==0,nn==0時(shí)侵状,是一個(gè)極大團(tuán),S為極大團(tuán)數(shù)量
    for(int i = 0; i < sn; ++i)  //遍歷P中的結(jié)點(diǎn)趣兄,sn==0時(shí)艇潭,搜索到終點(diǎn)
    {
        int v = some[d][i];  //取出P中的第i個(gè)結(jié)點(diǎn)
        for(int j = 0; j < an; ++j)  
         {
             all[d+1][j] = all[d][j];  
         }
        all[d+1][an] = v;
        //這里是將取出的v結(jié)點(diǎn)鲁纠,添加到R集合中改含,當(dāng)然是添加到下一個(gè)深度的R集合捍壤。
        int tsn = 0, tnn = 0;
        //用來分別記錄下一層中P集合和X集合中結(jié)點(diǎn)的個(gè)數(shù)
        for(int j = 0; j < sn; ++j)  if(mp[v][some[d][j]])  some[d+1][tsn++] = some[d][j];
        //更新P集合(當(dāng)然是下一層的P集合)鹃觉,保證P集合中的點(diǎn)都能與R集合中所有的點(diǎn)相連接
        for(int j = 0; j < nn; ++j)  if(mp[v][none[d][j]])   none[d+1][tnn++] = none[d][j];
        //更新X集合(當(dāng)然是下一層的X集合)盗扇,保證X集合中的點(diǎn)都能與R集合中所有的點(diǎn)相連接
        dfs(d+1, an+1, tsn, tnn);
        //遞歸進(jìn)入下一層
        some[d][i] = 0, none[d][nn++] = v;
        //完成后,將操作的結(jié)點(diǎn)抽减,放入X中卵沉,開始下面的尋找。
    }
}

Bron-Kerbosch 算法(Version 2)

??在上面這個(gè)方法中法牲,我們進(jìn)行了許多不必要的判斷史汗,例如在我們找到了極大團(tuán){1、2拒垃、3}之后停撞,依然去對(duì){1、3},{2戈毒、3}艰猬,{3}這些團(tuán)進(jìn)行了判斷,然而這些顯然不是極大團(tuán)冠桃。所以現(xiàn)在考慮的是對(duì)其進(jìn)行優(yōu)化,使程序不用進(jìn)行不必要的遞歸。

??當(dāng)我們將一個(gè)結(jié)點(diǎn)u,放入到R集合后,再取下一個(gè)結(jié)點(diǎn),取的必然是u的鄰居結(jié)點(diǎn)(因?yàn)樵俑翽和X時(shí)摊唇,會(huì)將不是鄰居結(jié)點(diǎn)的結(jié)點(diǎn)都過濾掉)旭寿。通俗的講就是如果取完u之后我們?cè)偃∨cu相鄰的點(diǎn)v也能加入到極大團(tuán)搭幻,那么我們只取u就好了贸桶,因?yàn)槲覀冇蓇開始遞歸水醋,已經(jīng)找到了u及其鄰居結(jié)點(diǎn)v等等結(jié)點(diǎn)構(gòu)成的極大團(tuán)了撮弧,沒有必要再去從v開始尋找極大團(tuán)贸辈,這會(huì)增加不必要的計(jì)算。至于v可能可以其他結(jié)點(diǎn)構(gòu)成另一個(gè)極大團(tuán),如果這個(gè)極大團(tuán)包括了u迈倍,那么由u開始就已經(jīng)找到了這個(gè)極大團(tuán)了;如果這個(gè)極大團(tuán)不包括u,那說明這個(gè)極大團(tuán)里面一定存在和u結(jié)點(diǎn)不相連的結(jié)點(diǎn)k弟蚀,那沒必要從v開始尋找這個(gè)極大團(tuán)了滞乙,從u的非鄰居結(jié)點(diǎn)k開始醉锅,一樣可以找到這個(gè)極大團(tuán)狸窘。這樣對(duì)u及其鄰居結(jié)點(diǎn)構(gòu)成的極大團(tuán),只需要從u開始尋找一次就可以了,接下來就直接從u的非鄰居結(jié)點(diǎn)k開始尋找極大團(tuán),這樣可以減少許多不必要的計(jì)算。

??例如上面的程序中我們從1開始尋找極大團(tuán),找到了由1及其鄰居結(jié)點(diǎn)構(gòu)成的極大團(tuán){1祝旷、2吻谋、3}闽撤,接下來我們就直接從1的非鄰居結(jié)點(diǎn)4號(hào)結(jié)點(diǎn)開始尋找極大團(tuán),可以找到極大團(tuán){4葱绒、2}荧止,最終所有的極大團(tuán)都被找到了。其中由2和3開始的這些不必要的計(jì)算都被省略阶剑,當(dāng)然在遞歸的內(nèi)部跃巡,我們也依然使用這種思想。而我們要想進(jìn)一步減少計(jì)算牧愁,我們就可以取鄰居盡可能多的u素邪,這樣讓我們要遍歷的點(diǎn)盡可能減少,但是其實(shí)沒必要如此猪半,尋找合適的u也會(huì)減少一定的效率兔朦。
設(shè)定關(guān)鍵點(diǎn) pivot vertex u偷线,只對(duì)關(guān)鍵點(diǎn)u自身和u的非鄰居結(jié)點(diǎn)進(jìn)行查找。
偽代碼如下:

Bron-Kerbosch Algorithm(Version 2)
R={}   //已確定的極大團(tuán)頂點(diǎn)的集合
P={v}  //未處理頂點(diǎn)集沽甥,初始狀態(tài)是所有結(jié)點(diǎn)
X={}   //已搜過的并且屬于某個(gè)極大團(tuán)的頂點(diǎn)集合

BronKerbosch(R, P, X):
   if P and X are both empty:
       report R as a maximal clique
   choose a pivot vertex u in P ? X
   for each vertex v in P \ N(u):   
       BronKerbosch1(R ? {v}, P ? N(v), X ? N(v))
       P := P \ {v}
       X := X ? {v}

下圖是Bron-Kerbosch Algorithm(Version 1)和Bron-Kerbosch Algorithm(Version 2)的對(duì)比


Bron-Kerbosch Algorithm(Version 2)

在右邊是沒有優(yōu)化的方法声邦,我們可以看到遞歸的次數(shù)非常的多,有太多不必要的計(jì)算摆舟。
左邊就是優(yōu)化后的方法亥曹,下面講解其中的具體步驟:
??這里每次會(huì)選擇鄰居結(jié)點(diǎn)多的那個(gè)結(jié)點(diǎn)當(dāng)作Pivot,所以一開始選擇4號(hào)結(jié)點(diǎn)做Pivot恨诱,我們也從4號(hào)結(jié)點(diǎn)開始進(jìn)行遞歸媳瞪,所以進(jìn)入下一層,得到R={4}照宝,P={1蛇受、2、3厕鹃、5兢仰、6},X={}熊响,P中的結(jié)點(diǎn)經(jīng)過篩選以后,將7號(hào)結(jié)點(diǎn)去除诗赌。
??我們從P中選2號(hào)結(jié)點(diǎn)(因?yàn)?號(hào)結(jié)點(diǎn)鄰居結(jié)點(diǎn)多)汗茄,同時(shí)也將2號(hào)結(jié)點(diǎn)作為這一層的Pivot 結(jié)點(diǎn),同時(shí)更新P集合铭若。這一層變?yōu)閧4洪碳、2},{1叼屠、3瞳腌、5},{}镜雨。
??我們從P中選1號(hào)結(jié)點(diǎn)(因?yàn)?號(hào)結(jié)點(diǎn)鄰居結(jié)點(diǎn)多嫂侍,當(dāng)然這里向圖中一樣選5號(hào)結(jié)點(diǎn)也是可以的),同時(shí)也將1號(hào)結(jié)點(diǎn)作為這一層的Pivot 結(jié)點(diǎn)荚坞,同時(shí)更新P集合挑宠。這一層變?yōu)閧4、2颓影、1}各淀,{3},{}诡挂。
??最后將P集合中的3號(hào)結(jié)點(diǎn)碎浇,放入R集合临谱,此時(shí)P集合和X集合都變?yōu)榭占詛4奴璃、2悉默、1、3}是極大團(tuán)溺健。到目前位置麦牺,我們的操作步驟和優(yōu)化之前是一樣的,只是這里每次都是選取的鄰居最多的結(jié)點(diǎn)放入(這樣是為了盡可能的減少遞歸次數(shù)鞭缭,其實(shí)按照原來的順序也是可以的)剖膳。和之前一樣的原因是,這是第一輪遞歸岭辣,我們?cè)诿恳粚臃湃隦集合的結(jié)點(diǎn)吱晒,就是該層的Pivot 結(jié)點(diǎn)。
??因?yàn)镻={}沦童,所以退回到上一層仑濒,也就是{4、2偷遗、1}墩瞳,{3},{}氏豌,然后將P中的結(jié)點(diǎn)3放入到X中喉酌,表示在這條路上,已經(jīng)探索過這個(gè)結(jié)點(diǎn)了泵喘。于是變?yōu)閧4泪电、2、1}纪铺,{}相速,{3},P集合為空集鲜锚,所以退回到上一層突诬,也就是{4、2}芜繁,{1攒霹、3、5}浆洗,{}催束,將1號(hào)結(jié)點(diǎn)移入X集合中,變?yōu)閧4伏社、2}抠刺,{3塔淤、5},{1}速妖,然后在P中選取下一個(gè)結(jié)點(diǎn)加入R集合中高蜂,在這一層中Pivot結(jié)點(diǎn)是1號(hào)結(jié)點(diǎn)(圖中為5號(hào)結(jié)點(diǎn),所以多了一步罕容,就是{4备恤、2、3}锦秒,{}露泊,{}這一步),所以凡是在P集合中并且是Pivot結(jié)點(diǎn)(也即是這里的1號(hào)結(jié)點(diǎn))的鄰居結(jié)點(diǎn)的旅择,從P集合中移除惭笑。比如這里3號(hào)結(jié)點(diǎn)在P集合中,且3號(hào)結(jié)點(diǎn)是1號(hào)結(jié)點(diǎn)的鄰居結(jié)點(diǎn)生真,所以將3號(hào)結(jié)點(diǎn)移除(因?yàn)槲覀円呀?jīng)通過1號(hào)結(jié)點(diǎn)沉噩,找到了同時(shí)包含1和3的極大團(tuán))。
??最后P集合中只有5號(hào)結(jié)點(diǎn)了柱蟀,所以將5號(hào)結(jié)點(diǎn)放入R集合中川蒙,并且更新P和X,變?yōu)閧4长已、2畜眨、5},{}痰哨,{}胶果,此時(shí)P和X都為空集匾嘱,所以{4斤斧、2、5}是一個(gè)極大團(tuán)霎烙。由4撬讽、2開始的這一層,我們已經(jīng)探索完了悬垃,所以要退回到{4}游昼,{1、2尝蠕、3烘豌、5、6}看彼,{}這一層廊佩。
??在這一層里囚聚,Pivot結(jié)點(diǎn)是2號(hào)結(jié)點(diǎn),所以P集合中和2是鄰居結(jié)點(diǎn)的那些結(jié)點(diǎn)將不會(huì)在放入R集合中标锄,所以將P集合中的6放入R集合中顽铸,更新P集合與X集合,得到{4料皇、6}谓松,{},{}践剂,所以{4鬼譬、6}是一個(gè)極大團(tuán)。
??現(xiàn)在由4開始的這條路徑舷手,我們也全部都走完了拧簸,所以需要退回到最開始的那一層,也就是{}男窟,{1盆赤、2、3歉眷、4牺六、5、6汗捡、7}淑际,{},將4號(hào)結(jié)點(diǎn)放入X集合中扇住,找到P集合中不和4號(hào)結(jié)點(diǎn)相連的那些結(jié)點(diǎn)春缕,這里只有7號(hào)結(jié)點(diǎn),所以將7號(hào)結(jié)點(diǎn)放入到R集合中艘蹋,同時(shí)更新P和X锄贼,得到{7},{5}女阀,{}宅荤,這里只有5號(hào)結(jié)點(diǎn)了,顯然選擇5號(hào)作為Pivot浸策,最后變?yōu)閧7冯键、5},{}庸汗,{}惫确,所以{7、5}就是一個(gè)極大團(tuán)。
??現(xiàn)在改化,已經(jīng)找出了圖中所有的極大團(tuán)昧诱,而且從圖中可以明顯的看出,我們遞歸的次數(shù)是比原來要少很多的所袁。
Bron-Kerbosch Algorithm(Version 2)完整的代碼為:


#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 130;
bool mp[maxn][maxn]; //表示結(jié)點(diǎn)之間的連接
int some[maxn][maxn], none[maxn][maxn], all[maxn][maxn];//分別是P集合盏档,X集合,R集合
int n, m, ans; //n表示結(jié)點(diǎn)數(shù)燥爷,m表示邊數(shù)蜈亩,ans表示極大團(tuán)數(shù)量
void dfs(int d, int an, int sn, int nn)
{
    if(!sn && !nn) ++ans;
    int u = some[d][0];  //選取Pivot結(jié)點(diǎn)
    for(int i = 0; i < sn; ++i)
    {
        int v = some[d][i];
        if(mp[u][v]) continue; 
        //如果是鄰居結(jié)點(diǎn),就直接跳過下面的程序前翎,進(jìn)行下一輪的循環(huán)稚配。顯然能讓程序運(yùn)行下去的,只有兩種港华,一種是v就是u結(jié)點(diǎn)本身道川,另一種是v不是u的鄰居結(jié)點(diǎn)。
        for(int j = 0; j < an; ++j)  
         {
             all[d+1][j] = all[d][j];  
         }
        all[d+1][an] = v;
        int tsn = 0, tnn = 0;
        for(int j = 0; j < sn; ++j)  if(mp[v][some[d][j]])  some[d+1][tsn++] = some[d][j];
        for(int j = 0; j < nn; ++j)  if(mp[v][none[d][j]])  none[d+1][tnn++] = none[d][j];
        dfs(d+1, an+1, tsn, tnn);
        some[d][i] = 0, none[d][nn++] = v;
        if(ans > 1000) return;    // 極大團(tuán)數(shù)量超過1000就不再統(tǒng)計(jì)
    }
}

int work()
{
    ans = 0;
    for(int i = 0; i < n; ++i) some[1][i] = i+1;
    dfs(1, 0, n, 0);
    return ans;
}

int main()
{
    while(~scanf("%d %d", &n, &m))
    {
        memset(mp, 0, sizeof mp);
        for(int i = 1; i <= m; ++i)
        {
            int u, v;
            scanf("%d %d", &u, &v);
            mp[u][v] = mp[v][u] = 1;
        }
        int tmp = work();
        if(tmp > 1000) puts("Too many maximal sets of friends.");
        else printf("%d\n", tmp);
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末立宜,一起剝皮案震驚了整個(gè)濱河市冒萄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橙数,老刑警劉巖尊流,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異灯帮,居然都是意外死亡崖技,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門钟哥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迎献,“玉大人,你說我怎么就攤上這事腻贰∮趸校” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵银受,是天一觀的道長践盼。 經(jīng)常有香客問我鸦采,道長宾巍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上趾盐,老公的妹妹穿的比我還像新娘扫夜。我一直安慰自己侈贷,他們只是感情好骤宣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布诞帐。 她就那樣靜靜地躺著淘正,像睡著了一般古徒。 火紅的嫁衣襯著肌膚如雪拓提。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天隧膘,我揣著相機(jī)與錄音代态,去河邊找鬼。 笑死疹吃,一個(gè)胖子當(dāng)著我的面吹牛蹦疑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萨驶,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼歉摧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了腔呜?” 一聲冷哼從身側(cè)響起叁温,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎核畴,沒想到半個(gè)月后券盅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膛檀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年锰镀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咖刃。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泳炉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嚎杨,到底是詐尸還是另有隱情花鹅,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布枫浙,位于F島的核電站刨肃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏箩帚。R本人自食惡果不足惜真友,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望紧帕。 院中可真熱鬧盔然,春花似錦桅打、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至站绪,卻和暖如春遭铺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恢准。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工掂僵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顷歌。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓锰蓬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眯漩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芹扭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1)這本書為什么值得看: Python語言描述,如果學(xué)的Python用這本書學(xué)數(shù)據(jù)結(jié)構(gòu)更合適 2016年出版赦抖,內(nèi)容...
    孫懷闊閱讀 12,466評(píng)論 0 15
  • 一些概念 數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)的邏輯結(jié)構(gòu)和物理結(jié)構(gòu)以及它們之間相互關(guān)系舱卡,并對(duì)這種結(jié)構(gòu)定義相應(yīng)的運(yùn)算,而且確保經(jīng)過這...
    Winterfell_Z閱讀 5,792評(píng)論 0 13
  • 目錄 0.樹0.1 一般樹的定義0.2 二叉樹的定義 1.查找樹ADT 2.查找樹的實(shí)現(xiàn)2.1 二叉查找樹2.2 ...
    王偵閱讀 7,198評(píng)論 0 3
  • 1.設(shè)計(jì)步驟 step1.分析需求誰是使用者队萤,將如何使用轮锥。5W1H——who what where when wh...
    王偵閱讀 1,051評(píng)論 0 1
  • 48期88班學(xué)員109-西安-流連 畢業(yè)感言:愛上理財(cái),愛上未來的你 時(shí)光冉冉要尔,轉(zhuǎn)瞬即逝舍杜,回想六月,幸運(yùn)的我在某個(gè)...
    自由自在_小牛閱讀 233評(píng)論 0 3