不了解極大團(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)的,例如{0翎卓、1}就不是極大團(tuán)契邀,因?yàn)橛衶0、1莲祸、2}的存在蹂安。這些不是極大團(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í)例:
在最開始中芙盘,集合P會(huì)初始化為所有的結(jié)點(diǎn)驯用,其他集合為空集。
首先儒老,我們將集合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)連接的榆苞。
同樣的,我們繼續(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}街夭。
最后同樣的我們將P集合中的結(jié)點(diǎn),放入R集合躏筏。此時(shí)P集合X集合都為空集板丽,說明這個(gè)團(tuán)已經(jīng)不能再擴(kuò)充了,所以{1趁尼、2埃碱、3}就是一個(gè)極大團(tuán)。
??我們從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)顷蟀。
??在完成判斷后酒请,退回到上一層,也就是圖中的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)了遥皂。
??同樣的力喷,我們將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)的這條路了芹橡。
將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)骂倘。
我們的遍歷當(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)袖肥。
每一次遍歷中椎组,所生成的集合如上圖的右下角所示寸癌,可以很明顯的看出兩個(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ì)比
在右邊是沒有優(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;
}