JA EN
体系計算量と評価
·無料·12分で読めます

NP完全を1から — 「解けない」ではなく「検証は速い」

NPはNon-Polynomialの略ではありません。「答えを見せられたら速く確認できる」というクラスの話です。P vs NP・還元・NP完全を前提知識ゼロから積み上げ、シフト表や配送計画といった現場の問題にそれがどう現れるかまで見ます。

対象textタスクalgorithm

数独を思い出してほしい

数独を解くのは骨が折れます。候補を絞って、行き詰まって戻って、をひたすら繰り返す。ところが、誰かが埋め終わった盤面を渡されて「これ合ってる?」と聞かれたら、確認は一瞬です。各行・各列・各ブロックに1から9が1回ずつ入っているか目で追うだけ。

解くのは大変、確かめるのは楽。 この非対称さが、計算量理論でいちばん有名な未解決問題 P vs NP の正体です。

先に誤解を1つ潰します。NP は Non-Polynomial(非多項式)の略ではありません。Nondeterministic Polynomial(非決定性多項式時間)の略で、意味はむしろ逆寄りです。「この問題はNPに属する」は「難しい」ではなく「答えの確認が速い」と言っています。取り違えたまま設計会議で「NPなので無理です」と言うと、技術的に間違ったことを言っていることになります。

P — 多項式時間で解ける問題

入力の大きさを nn とします。頂点が nn 個のグラフ、要素が nn 個のリスト、といった規模のことです。実行時間が nn の多項式、つまり nnnlognn\log nn2n^2n3n^3 …… で抑えられる問題の集まりを P と呼びます。ソートも最短経路も素数判定もここに入ります。

なぜ多項式という一見ざっくりした線引きなのか。合成しても壊れないからです。多項式時間の処理の中で別の多項式時間の処理を多項式回呼んでも、全体は多項式時間に収まります。部品を組み合わせても分類が変わらないうえ、命令セットやメモリの違いも多項式の範囲に吸収されるので、「Pに属するか」は特定のCPUではなく問題そのものの性質になります。

もちろん n100n^{100} は多項式でも実用にはなりません。オーダー記法が何を捨てているかは計算量を1から理解するで扱っています。

NP — 答えを見せられたら速く確認できる

NP は「解ける」ではなく「検証できる」で定義されます。

「はい/いいえ」で答える問題を考えます。「はい」が答えになる入力には、そう主張する証拠(certificate)を添えられるとしましょう。数独なら埋め終わった盤面、「総距離1000km以内で全都市をまわる経路はあるか」なら具体的な巡回順、「この論理式を真にする割り当てはあるか」なら割り当てそのものです。

証拠の長さが入力サイズの多項式以内に収まり、その検証が多項式時間で終わるなら、その問題は NP に属します。

xL    w  [wp(x)    V(x,w)=1]x \in L \iff \exists w \;\big[\, |w| \le p(|x|) \;\wedge\; V(x, w) = 1 \,\big]
(1)

式(1)を日本語に戻します。xx は入力、LL は「答えがはいになる入力の集まり」、ww は証拠、pp は何らかの多項式、VV は多項式時間で動く検証器です。読み下せば「xx の答えがはいであることは、多項式サイズの証拠 ww が存在して検証器がそれを受理することと同じ」。証拠を見つける手間はどこにも書かれていない、そこが肝心です。誰かが持ってきた前提で、確認だけを問うています。

PNP\mathrm{P} \subseteq \mathrm{NP} はすぐ分かります。自力で速く解けるなら証拠は空でよく、検証器が自分で解き直せばいい。未解決なのは逆向き、P=NP\mathrm{P} = \mathrm{NP} か? です。クレイ数学研究所が2000年に選んだミレニアム懸賞問題の1つで、賞金は100万ドル。多くの研究者は PNP\mathrm{P} \ne \mathrm{NP} を予想していますが、どちら向きの証明も出ていません。

指数がどれだけ非常識か

「総当たりすれば」が通用しない規模の話をしています。

FIG 1nを動かしながら対数軸に切り替えると、多項式と指数の差が「定数倍」ではなく「桁」であることが見える

50個の要素から部分集合を全部試すと 2501.13×10152^{50} \approx 1.13\times10^{15} 通り。仮に1秒で10億通り調べられる計算機でも約13日かかります。100個なら 21001.27×10302^{100} \approx 1.27\times10^{30} 通りで、同じ計算機では宇宙の年齢の千倍以上です。

本当に厄介なのはここです。計算機を1000倍速くしても、扱える nn は10ほどしか増えません210=10242^{10}=1024 だから)。ハードウェアの進歩を待つ作戦が原理的に効かない、ほとんど唯一の領域です。

還元 — 問題を別の問題に着せ替える

個々の問題を別々に論じるのではなく、問題どうしを翻訳して比べる道具を入れます。

問題 A の入力を多項式時間で問題 B の入力に変換でき、しかも「変換後の B の答え」がそのまま「元の A の答え」になるとき、A は B に還元できるといい ApBA \le_p B と書きます。

例として、時間割づくりグラフの彩色に着せ替えます。授業を1つずつ頂点にし、同じ先生・教室・クラスを使う(=同時に置けない)2授業の間にを引き、コマをとする。すると「隣り合う頂点に同じ色を塗らない」が、そのまま「衝突する2授業を同じ時間に置かない」になります。翻訳の手間は授業数と衝突数に比例するだけなので多項式時間。彩色を速く解けるなら時間割も速く解ける、というわけです。

p\le_p が不等号の形なのは偶然ではありません。ApBA \le_p B は「A は高々 B と同じくらいの難しさ」を意味します。そしてここが最も間違えられる場所です。目の前の問題が難しいと示したいなら、翻訳の向きは「既知の難問 → 自分の問題」でなければなりません。 逆向きを作っても「自分の問題は既知の難問より簡単かもしれない」としか言えず、難しさの証明にはなりません。

NP完全 — NPの中で最も硬い一群

NP完全の定義は2条件です。

  1. その問題自身が NP に属する(答えの検証が速い)
  2. NP に属するあらゆる問題が、その問題に多項式時間で還元できる

2つ目が強烈です。無限にある問題すべてが1つに翻訳できる、と主張しています。そんなものが本当にあるのか——1971年に Stephen Cook が、独立に1973年に Leonid Levin が、SAT(論理式の充足可能性問題)がまさにそれだと示しました。

土台が1つできれば、あとは芋づる式です。SAT から新しい問題への還元を1本示せば、SAT 経由でNP全体がその問題に還元できたことになる。1972年に Richard Karp がこの方法で、クリーク・頂点被覆・グラフ彩色・部分和・ハミルトン閉路など21個の組合せ問題を一気にNP完全と示しました。現場でよく見かける顔ぶれです。

帰結が効きます。NP完全問題のどれか1つに多項式時間アルゴリズムが見つかれば、その瞬間にNP全体が P に落ちます。 逆に、半世紀以上どれ1つ崩せていないことが、PNP\mathrm{P} \ne \mathrm{NP} を信じる実務家なりの根拠になっています。

NP困難との違い

NP困難は上の条件2だけを満たすもので、条件1(NPに属する)は要りません。だからNP完全はNP困難の一部です。停止問題はNP困難ですが、決定不能なのでNPには属しません。

もう1つ紛らわしいのが決定問題と最適化問題の別です。NP完全は「はい/いいえ」で答える決定問題に使う言葉で、「総距離 KK 以下の巡回路は存在するか」はNP完全、「最短の巡回路を求めよ」はNP困難と呼び分けます。現場で頼まれるのはほぼ後者ですが、難しさの議論は前者に置き換えるのが定石です。

「NP完全だ」と分かった後にやること

NP完全だと分かることは諦める理由ではなく、戦略を切り替える合図です。手は4つあります。

1. 既製のソルバに載せる。 SATソルバ、MILP(混合整数計画)ソルバ、CP-SAT(制約プログラミング)ソルバは、最悪ケースこそ指数ですが実データの構造を使い切ります。「NP完全=手が出ない」ではまったくない。

2. 最適解をあきらめる。 保証付きの近似(距離が三角不等式を満たす巡回セールスマン問題には、最適の1.5倍以内を保証する古典的手法があります)と、保証はないが実用的なヒューリスティックの2種類。後者の使いどころは焼きなましと遺伝的アルゴリズムにまとめてあります。

3. 入力の構造に賭ける。 実データのパラメータが小さければ多項式に落ちることがあります。部分和・ナップサックは目標値 WW を使ったDPで O(nW)O(nW) ですが、WWに比例するので「擬似多項式」と呼ばれ、WW が巨大だと破綻します。

4. 問題を定義し直す。 制約を1つ落とすとPに落ちることがあります。SATは1節に変数3個ならNP完全ですが、2個(2-SAT)なら多項式時間です。「この制約、本当に必要ですか」と聞き返すのが最も安上がりな高速化になることがあります。

コードで見る境界

部分和問題(選んだ数の合計をちょうど target にできるか)で、総当たりとDPを並べます。

from itertools import combinations

def subset_sum_bruteforce(nums, target):     # O(2^n)
    for r in range(len(nums) + 1):
        for c in combinations(nums, r):
            if sum(c) == target:
                return True
    return False

def subset_sum_dp(nums, target):             # O(n * target) 擬似多項式
    reachable = [False] * (target + 1)
    reachable[0] = True
    for x in nums:
        for s in range(target, x - 1, -1):   # 各数を1回だけ使う
            reachable[s] |= reachable[s - x]
    return reachable[target]

下のDPが効くのは、覚えるべき部分問題が target+1target+1 個しかないからです。この「表が現実の大きさに収まるか」が、多項式と指数を分ける境目そのものになっています。

FIG 2編集距離のDP表。部分問題が m×n 個しかないから最後まで埋まりきる。NP完全な問題では覚えるべき部分問題の数そのものが指数になり、この表が作れない

表を埋める考え方は動的計画法を1から解説で詳しく扱っています。

現場ではこう使う

誰が、いつ。 バックエンド/最適化のエンジニアが「シフト表を自動で作りたい」「トラック20台の配送順を決めたい」と頼まれたとき。インフラ担当がVMやコンテナの配置(=ビンパッキング)を詰めるとき。コンパイラ屋がレジスタ割当(=グラフ彩色)を実装するとき。パッケージマネージャの依存解決を書くとき(多くの実装が内部でSATソルバを使っています)。動作は共通で、「これは既知のNP完全問題のどれかではないか」をまず疑うこと。当てはまるなら、自前の総当たりを書き始める前にソルバへ載せる判断ができます。

触るもの。 OR-Tools の CP-SAT なら、打ち切りは max_time_in_seconds、並列度は num_search_workers、既存解を初期値に渡すウォームスタートは AddHint。MILPソルバなら「最適解との差をどこまで許すか」の relative_mip_gap(Gurobiでは MIPGap)が実運用の主役です。モデルの受け渡しは LP形式・MPS形式。SAT/SMTを直接使うなら Z3 や MiniSat 系。

知らないと事故になる落とし穴。

設計レビューで問われる形。 「この問題がNP完全だと考える根拠は?」——正解は「感覚的に難しいから」ではなく「既知のNP完全問題 X から、自分の問題への多項式時間還元が作れるから」。そしてほぼ必ず「Pに落ちる特殊ケースはあるか」が続きます。両方に答えられると、議論が「諦める/諦めない」から「どう設計を変えるか」へ移ります。

まとめ

コメント

コメントにはログインが必要です