最大クリーク問題 試行
いままでと全然変えてみてある辺(i,j)を含む、順序上 j以下の頂点で構築できる最大クリークとか考えてみた。
サイズcのクリークがある時、全点から接続している点はc+1のクリークって考えると凄くコンパクト。
でも2^cとか含むだろうけどね。
n,m = gets.split.map(&:to_i) G = {} U = {} V = n.times.to_a E = m.times.map do gets.split.map(&:to_i).sort end E.sort! V.each do |v| G[v] = [] U[v] = [] end E.each do |from, to| G[from] << to G[to] << from U[to] << from end MEMO = {} def dfs(clique, c) clique.sort! s,t = clique.first, clique.last status = clique.join(' ') return MEMO[status] if MEMO[status] nclique = clique.inject(U[t]){|n, v| n & G[v] }.sort nc = nclique.empty? ? c : nclique.map {|nv| dfs(clique + [nv], c+1) }.max p clique: clique, c: nc MEMO[status] = nc end E.each do |e| puts "#{e} #{dfs(e, 2)}" end
なんとなくこのコードが引っかかる。
再帰呼び出しc回でサイズcの極大クリークが見つかる、サイズcのクリークは2^c - 1個のクリークがあるけど、なんとなくうまく枝刈りするとサイズがcの極大クリーク(他のクリークに含まれないの意味)をc^2のオーダーで抜けれそうな気がするんだよね。
例えばこんな感じのコードかなぁ。
n,m = gets.split.map(&:to_i) VISITED = {} G = {} V = n.times.to_a E = m.times.map { gets.split.map(&:to_i).sort } V.each {|v| G[v] = [] } E.each do |from, to| G[from] << to G[to] << from end def dfs(clique, max) clique.sort! status = clique.join(' ') return max if VISITED[status] VISITED[status] = true if clique.size > max max = clique.size # p clique: clique, max: max end head, *body = clique nclique = body.inject(G[head]){|n, v| n & G[v] } if clique.size + nclique.size > max max = nclique.inject(max) {|m, u| dfs(clique + [u], m) } end max end if n == 0 puts 0 elsif m == 0 puts 1 else puts E.inject(0){|max, e| dfs(e, max) } end
下記入力に対して
8 14 0 1 0 2 0 3 1 2 1 3 2 3 0 4 0 5 0 6 0 7 4 7 4 5 5 6 6 7
こういう動き
{:clique=>[0, 1], :max=>2} {:clique=>[0, 1, 2], :max=>3} {:clique=>[0, 1, 2, 3], :max=>4} {:clique=>[0, 1, 3], :max=>4} {:clique=>[0, 2], :max=>4} {:clique=>[0, 3], :max=>4} {:clique=>[1, 2], :max=>4} {:clique=>[1, 3], :max=>4} {:clique=>[2, 3], :max=>4} {:clique=>[0, 4], :max=>4} {:clique=>[0, 5], :max=>4} {:clique=>[0, 6], :max=>4} {:clique=>[0, 7], :max=>4} {:clique=>[4, 7], :max=>4} {:clique=>[4, 5], :max=>4} {:clique=>[5, 6], :max=>4} {:clique=>[6, 7], :max=>4}
サイズcのクリークの全点から接続している点は必ずサイズc+1のクリークで、これまでに見つかった最大クリークのサイズをmaxとすると、サイズc+1のクリークがmax - c個より多く存在しないとmax + 1のサイズのクリークはないので枝刈ってみた。
Submission #1100279 - AtCoder Beginner Contest 002 | AtCoderの結果
これcarraghanのアルゴリズムっぽい。。。
https://www.jstage.jst.go.jp/article/ciqs/2006/0/2006_0_JP04/_pdf
あけましておめでとうございます。
本年もよろしくお願いします。
昨年は最大クリーク問題で頭イタイ人に思われたりして、一時期、心を病んだ状態でしたが、すごく楽しかったなぁとおもっています。
ことしも全然やるつもりで、まさにブログ名どおりの高温処理済みになったなぁとおもっています。
続: 最大クリーク問題
なんかいろいろやってこんな形に落ち着いた。
簡単にいうとまず適当にソートして、有向グラフに変換してDPする。
dp[j][c]はj以下の頂点を使用したjを含むサイズcのクリークの文字列表現の配列。
後続の頂点iからはこのクリークの頂点にすべて辺があれば、サイズc+1のクリークなのでdp[i][c+1]に追加する。
このクリークの頂点にすべて辺があればの部分は、rubyなので配列を&した、他の言語だとbitsetみたいなライブラリで行うほうがよいと思う。
belongs_max_cliqueは前回のソースを引き継いでいるので現在は適当ではないかもしれないけど、ある頂点vに隣接する誘導グラフから、最大クリークサイズをだす場合はbelongs_max_clique(G[v]) + 1とすればよいようになっている。
#!/usr/bin/env ruby V, m = gets.split.map(&:to_i) # read Graph Infomation G = {} # Initialize Graph V.times {|v| G[v] = []} m.times do from, to = gets.split.map(&:to_i).sort G[from] << to G[to] << from end def belongs_max_clique(node = V.times.to_a, limit = V) subgraph = {} node.sort! node.each_index{|i| subgraph[i] = [] } node.each_with_index do |u, i| i.times do |j| w = node[j] subgraph[i] << j if G[u].include?(w) end end dp = Array.new(node.size + 1){ Array.new(limit + 1){ [] } } node.each_index{|i| dp[i][1] << i.to_s } max_clique_size = (1 ... limit).find do |c| found_clique = false node.each_index do |i| subgraph[i].each do |j| dp[j][c].each do |clq| clique = clq.split.map(&:to_i) shared = clique & subgraph[i] if shared == clique clique << i dp[i][c + 1] << clique.join(' ') dp[node.size][c + 1] << clique.join(' ') found_clique = true end end end end !found_clique end max_clique_size || limit end puts belongs_max_clique
前回指摘をもらったグラフだとDPした結果は以下のようになる
[[], ["0"], [], [], [], [], [], [], []], [[], ["1"], ["0 1"], [], [], [], [], [], []], [[], ["2"], ["0 2", "1 2"], ["0 1 2"], [], [], [], [], []], [[], ["3"], ["0 3", "1 3", "2 3"], ["0 1 3", "0 2 3", "1 2 3"], ["0 1 2 3"], [], [], [], []], [[], ["4"], ["0 4"], [], [], [], [], [], []], [[], ["5"], ["0 5", "4 5"], ["0 4 5"], [], [], [], [], []], [[], ["6"], ["0 6", "5 6"], ["0 5 6"], [], [], [], [], []], [[], ["7"], ["0 7", "4 7", "6 7"], ["0 4 7", "0 6 7"], [], [], [], [], []],
入力はこんな感じ
8 14 0 1 0 2 0 3 1 2 1 3 2 3 0 4 0 5 0 6 0 7 4 7 4 5 5 6 6 7
dp[node.size][c]は別になくてもよいけど、サイズcのクリーク( c >= 2)の全列挙用につくっている。
こんな感じになる。
[], ["0 1", "0 2", "1 2", "0 3", "1 3", "2 3", "0 4", "0 5", "4 5", "0 6", "5 6", "0 7", "4 7", "6 7"], ["0 1 2", "0 1 3", "0 2 3", "1 2 3", "0 4 5", "0 5 6", "0 4 7", "0 6 7"], ["0 1 2 3"]
追記: コメントに計算量のことを書いて質問をいただきました。現在のコードがc^2 * v * eかと言われれば、ご指摘の通りたぶん違うんじゃないかとおもってきました。ただ自分的にはc^2 * v * eにまでおそらく落とせると思っているのでそこまで計算量を落としたアルゴリズムを作りたいと思っています。
メモ: cのループをnodeのループの中に入れて、サイズcのクリークの全列挙をやめると計算量は落ちるな。でもやはりサイズcのクリーク数に依存しそうだから、サイズcのクリークの個数に対して、cかc^2になりそうかな。計算量は落ち着かないけど、これならできそう。
最大クリーク問題
まだうまく証明できていないかもしれないけど、これで解けてると思う解けていないです。
別の実装を考えたのでコードを消しました。
belongs_max_cliqueではある頂点を含む極大クリークのサイズを返していて、それを全点に対して行いその最大のものを出力している。
belongs_max_cliqueは任意の1頂点(v)とそれに隣接する頂点集合をUとすると、誘導部分グラフG[U]に対し、その次数をまず計算する。 Uの次数の少ないものから、順に切断していくと最終的に他の全ての点と同じ次数になるので、その時残っているグラフがvを含む最大クリークとなる。 クリークのサイズは次数 + 1すればよい。
(証明)
- 最大クリークはそのグラフの頂点のどれかを含んでいる(自明)
- ある頂点を選んでその頂点を含む極大クリークがわかれば、それを全点でおこなうことで最大クリークがわかる。(問題ないとおもう)
- G[U]はvとvに隣接する頂点集合からなる誘導部分グラフであるので、vを含むクリークはすべて含まれる。(多分、自明)
- G[U]はvとvに隣接する頂点集合からなる誘導部分グラフであるので、vの次数は必ず他の頂点の次数以上である。(Uの頂点数がnならG[U]の頂点の次数はn-1より大きくなることはなく、vの次数はn-1)
- G[U]が完全グラフ(Kn)のとき、その全ての頂点の次数はn-1となり、頂点数はnとなり、停止条件となる。(n-正則グラフの場合があり、判定できてない)
- G[U]からvの次数未満の最小の次数をもつ頂点を1点切断した頂点集合をU'とするとG[U']も必ずvを含む極大クリークが含まれる。(問題あり)
- U'をUとして5に戻っても矛盾はない。(問題ないとおもう)
以上より、このアルゴリズムは停止条件をもち、有限時間内に停止する。 またその時、このグラフに含まれる極大クリークのうち、最大のサイズを示す。
計算量はちょっと自信がないけど、頂点数をv , 次数の平均をdとすると O(v * d^2) じゃないかとおもう。 次数の平均はE / VなのでO(E^2 / V)かな。
以下、コーナーケースとして指摘されそうな例
- G[U]が2つの完全グラフ(Kn,Km)を含み、その節点がvのとき、vは次数n+m - 2をもち、vを除くKn,Kmに含まれる点はそれぞれn-1,m-1の次数をもつ。
- n < mのとき、Knの点から順に切断され、最終的にKmだけ残りすべての頂点がm-1の次数をもつ。
- n = mのときはvをのぞく全ての点がn-1の次数を持ち、そのうち1点を切断することで、n < mと同じ状況になる。
- G[U]が2つの完全グラフ(Kn,Km)を含み、vを除くKn,Km上の点を結ぶ接続がある場合も、上記のケースでの切断する順序に変化があるだけで、同様に最大クリークを返す。
- G[U]が2つの完全グラフ(Kn,Km)を含み、vを除くKn,Km上の点にすべて接続がある場合、それは完全グラフKn+m-1であり、同様に最大クリークを返す。
TODO: 課題
- 全ての次数が同じだけど、片側に完全Kn,もう片側にn-1正則グラフがある場合
@tinsep19 最小の頂点が複数ある時ってどれを選びますか(図で真ん中をvとして選んだ場合) pic.twitter.com/Lh7lLTTMwG
— (nは自然数) (@n_vip) December 1, 2016
@n_vip 興味持っていただき。ありがとうございます。このケースだと現在の実装だと右側を消すとは限らないですね。
— TANIGUCHI Kousuke (@tinsep19) December 1, 2016
vを含む極大クリークが返されるとしている部分が間違っていますね。修正してまた挑戦したいとおもいます。
この状況を把握するのはおそらく無理、右側と左側にリンクがある場合もあるので、フローとかで優先順位を確定できない限り難しい。もしかすると現在の実装でも、それなりに正しいものを出すかもしれないけど、コーナーケースのデータは否定できない。
- 案1. V.each前にsortするかbelongs_max_clique内のsortでabsを使用しているが、これを変えて、全体の順序のなかで順序付けして、整合性が取れるようにする。もしくは現在の状況で整合性がとれていることを示す必要がある。
- 案2. 完全グラフかどうかはキーの数 = 次数 + 1かどうかで判定できるので、そうでない場合はスキップする。この場合も全体のなかで整合性が取れている必要がある。もしくは優先順位を変えて再度実施する。
とりあえず完全グラフかどうか頂点数と次数で判定して、正則グラフの場合は-1を返すようにしてみた。ただ全体の整合性がとれるのかというと違う。もちろん右側と左側の別の頂点でbelong_max_cliqueは呼ばれるけど、全ての頂点がn-正則グラフと隣接しているというような状況はある。
グラフ理論について、今のところわかったこと
任意のグラフをGとし、その彩色数をとあらわす。 1点uを加えG'とするとき、その接続数がk未満の場合には必ずが成立する。
証明
であるGからk未満の点を選択するとき、uはかならずk色の中から彩色の色を選択できる。 よって上記が成りたつ。
(証明終わり)
任意のグラフをGとし、その彩色数をとあらわす。 1点uを加えG'とするとき、その頂点が全点に接続する場合には必ずが成立する。
証明
であるGに頂点uを加え、全点に接続するならば、uは必ず全色に接続し、k+1色目が必要になる。
もしであるなら、彩色数が彩色できる最小の色数という前提に反する。
またで十分であり、にはならない。
(証明終わり)
上記より
- 頂点数v, 彩色数kをあたえられたとき、接続数をk未満とする頂点を追加する操作を繰り返すことで、頂点数vで彩色数k以下を満たすグラフを作ることができる。
- vがkに対し、十分大きい時、頂点数v,彩色数kのグラフを作成できる。
- 無向グラフの頂点を適当に順序付けして、その任意の2頂点i,j (i < j)に辺があるとき、jからiに対し向きをつける。全ての頂点に対し、この出力辺をk本以内にできるとき、k+1彩色可能。ただし彩色数(Chromatic number)と一致するとは限らない。
- 上記からn正則グラフはn+1彩色可能、ただし彩色数(Chromatic number)と一致するとは限らない。
- ピーターセングラフがわかりやすい例
多分これは正則グラフではBrooksの定理と一致して、一般のグラフでは上限をよく押さえつけるものになるかな。
Brooks' theorem - Wikipedia
完全2部グラフKi,jを彩色した色クラスをそれぞれ閉路にしたとき(i,j >= 2)
- その彩色数は4(i,jが共に偶数),5(i,jが偶数、奇数),6(i,j共に奇数)になる。
偶閉路が2彩色、奇閉路が3彩色できることによる。
完全2部グラフKi,jを彩色した色クラスを完全グラフにしたとき
- その彩色数はi+jになる。
Ki+jと同一なことによる。
奇閉路に一点加えて彩色数を4とするためには全点に接続しないといけない。
俺の中でP=NPの機運が高まっている。
彩色数判定へのアプローチ
最初は彩色数kのグラフは2部グラフマッチングか最大流にできるとおもっていたのですが、制限付きナップサック問題かなにかに帰着できそうな気がしています。
たしかに組み合わせ問題なのでナップサック問題になってもおかしくなさそうです。
現在、v,e,kのみの数値でのプロトタイプを作成しています、ピーターセングラフはちゃんと3とでますが、2部グラフKn,mが2で出ません。
ただしある程度法則性があってn = mのときnででますし、m = n+1のときmででます。m = n + 2のとき、(m + n) / 2ででていて、完全2部グラフは完全グラフ内に含まれる。というのが仮説としてでてきました。
3彩色問題はヒューリスティックな感じの判定プログラムはつくれそうな気がします。
ただ理論的裏付けがなく、それが判定式となるのかはまだまだ研究中です。
完全グラフのマイナー
完全グラフばかり考えていたのですが、マイナーを考えないと本質にたどりつけなさそうなので考えています。
Kn を彩色数を変えずにKmだけ分離していく(強連結を置換していく)には、完全2部グラフが必要になる。
(片方はmだけど、もう片方が、m+1かn-1か、それともm + n / 2みないなものなのかが不明)
みたいな感じです。