グラフ彩色問題

無向グラフ $G=(V,E)$ が与えられたとき、グラフ彩色問題は、隣接するノードが異なる色を持つように各ノードに色を割り当てることを目的とします。 より具体的には、色の集合 $C$ に対して、すべての辺 $(u,v)\in E$ について $\sigma(u)\neq \sigma(v)$ となるような割り当て $\sigma:V\rightarrow C$ を求めます。グラフ彩色問題は QUBO 式として容易に定式化できます。 $V=\lbrace 0,1,\ldots ,n−1\rbrace$、$C=\lbrace 0,1,\ldots ,m−1\rbrace$ とします。 $n\times m$ のバイナリ変数行列 $X=(x_{i,j})$ を導入し、$x_{i,j}=1$ はノード $i$ に色 $j$ が割り当てられることを表します。

ワンホット制約

各ノードにちょうど1つの色を割り当てる必要があるため、$X$ の各行はワンホットでなければなりません:

\[\begin{aligned} \text{onehot}&= \sum_{i=0}^{n-1}\Bigl(\sum_{j=0}^{m-1}x_{i,j}==1\Bigr)\\ &=\sum_{i=0}^{n-1}\Bigl(1-\sum_{j=0}^{m-1}x_{i,j}\Bigr)^2 \end{aligned}\]

隣接ノードは異なる色

各辺について、その端点は同じ色を共有してはなりません。これは以下のようにペナルティ化できます:

\[\begin{aligned} \text{different}&= \sum_{(u,v)\in E}x_u\cdot x_v\\ &=\sum_{(u,v)\in E}\sum_{j=0}^{m-1}x_{u,j}x_{v,j} \end{aligned}\]

QUBO 目的関数

これらの式を組み合わせることで、QUBO 目的関数が得られます:

\[\begin{aligned} f &= \text{onehot}+\text{different} \end{aligned}\]

この目的関数は、グラフの有効な $m$-彩色が存在する場合にのみ最小値 0 を達成します。

QUBO++ による定式化

任意の平面グラフは最大4色で彩色できるため、16 ノードの平面グラフと $m=4$ 色を例として使用します。以下の QUBO++ プログラムがこのインスタンスを解きます:

#include <qbpp/qbpp.hpp>
#include <qbpp/easy_solver.hpp>
#include <qbpp/graph.hpp>

int main() {
  const size_t n = 16;
  std::vector<std::pair<size_t, size_t>> edges = {
      {0, 1},   {0, 2},   {0, 4},   {1, 3},   {1, 4},   {1, 7},   {2, 5},
      {2, 6},   {3, 7},   {3, 13},  {3, 15},  {4, 6},   {4, 7},   {4, 14},
      {5, 8},   {6, 8},   {6, 14},  {7, 14},  {7, 15},  {8, 9},   {8, 12},
      {9, 10},  {9, 11},  {9, 12},  {10, 11}, {10, 12}, {10, 13}, {10, 14},
      {10, 15}, {11, 13}, {12, 14}, {13, 15}, {14, 15}};
  const size_t m = 4;

  auto x = qbpp::var("x", n, m);

  auto onehot = qbpp::sum(qbpp::vector_sum(x) == 1);
  auto different = qbpp::Expr(0);
  for (const auto& e : edges) {
    different += qbpp::sum(qbpp::row(x, e.first) * qbpp::row(x, e.second));
  }

  auto f = onehot + different;

  f.simplify_as_binary();
  auto solver = qbpp::easy_solver::EasySolver(f);
  auto sol = solver.search({{"target_energy", 0}});

  std::cout << "onehot = " << sol(onehot) << std::endl;
  std::cout << "different = " << sol(different) << std::endl;

  auto node_color = qbpp::onehot_to_int(sol(x));

  qbpp::graph::GraphDrawer graph;
  for (size_t i = 0; i < n; ++i) {
    graph.add_node(qbpp::graph::Node(i).color(node_color[i] + 1));
  }
  for (const auto& e : edges) {
    graph.add_edge(qbpp::graph::Edge(e.first, e.second));
  }

  graph.write("graph_color.svg");
}

このプログラムでは、まず $n\times m$ のバイナリ変数行列 x を定義し、上記の定式化に従って式 onehotdifferentf を構築します。得られた QUBO を目標エネルギー 0 で Easy Solver を用いて解き、解を sol に格納します。

次に、sol における onehotdifferent の値を出力します。また、sol(x)qbpp::onehot_to_int() を適用して、各ノードに割り当てられた色を格納する node_color を計算します。

最後に、qbpp::graph::GraphDrawer を使って彩色されたグラフを描画します。各ノード i は色番号 node_color[i] + 1 で彩色されます。

関数 qbpp::onehot_to_int() は $[0,m−1]$ の範囲の整数ベクトルを返し、各エントリはワンホット行列の対応する行における 1 の位置を示します。行が有効なワンホットベクトルでない場合、その行に対して $−1$ を返します。 この場合、ノードの色は $-1 + 1 = 0$ となり、ノードは色 0(白)で描画されます。

$m=4$ の結果

このプログラムの出力は以下の通りです:

onehot = 0
different = 0

したがって、有効な 4-彩色が見つかりました:

グラフ彩色問題の解

$m=3$ の結果

同じプログラムを $m=3$ で実行すると、以下の出力が得られます:

onehot = 1
different = 0

この出力は、ソルバーがちょうど1つのノードに色を割り当てられなかったことを示しています(つまり、1つの行がワンホットではありません)。結果のグラフでは、ノード 7 が未彩色のままです:

$m=3$色でのグラフ彩色問題の解


Back to top

Page last modified: 2026.04.04.