4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【配送最適化入門】Lin-Kernighan法とは?④

4
Last updated at Posted at 2026-06-12

この記事について

前回は3-optを説明しました。今回は局所探索の中でも特に高精度なLin-Kernighan法(LK法) を説明します。

LK法は「可変長の辺交換」を行うことで、2-optや3-optよりも高品質な解が得られることで知られています。少し複雑なアルゴリズムですが、直感的なイメージから理解していきましょう。

配送最適化入門シリーズの記事一覧はこちら。

記事 内容
配送最適化とは?基礎概念
近傍法の説明
2-optの説明
3-optの説明
④(本記事) Lin-Kernighan法の説明
焼きなまし法(SA)の説明
遺伝的アルゴリズム(GA)の説明
VRPとは?
VRPをアルゴリズムで解く

Lin-Kernighan法とは?

Lin-Kernighan法は1973年にLinとKernighanが提案した高精度ローカルサーチです。

S. Lin and B. Kernighan, "An Effective Heuristic Algorithm for the Traveling-Salesman Problem," Operations Research, vol. 21, no. 2, pp. 498-516, 1973.

2-optは「2辺」、3-optは「3辺」と固定の辺数を交換するのに対して、LK法は交換する辺の数を動的に決めるという発想が核心です。

核心のアイデア

LK法の基本的な考え方は「連鎖的な辺の追加・削除」です。2-optが「2本の辺を入れ替える」のと同じ発想ですが、LK法では入れ替える辺の本数を状況に応じて動的に増やすところが違います。

「削除→追加」の連鎖のイメージ

ルート上の都市 $t_1$ を起点として、以下の操作を繰り返します。

ステップ 操作 意味
辺 $e_1 = (t_1, t_2)$ を削除 距離 $|e_1|$ 分だけ「節約」
辺 $x_1 = (t_2, t_3)$ を追加 距離 $|x_1|$ 分だけ「消費」
辺 $e_2 = (t_3, t_4)$ を削除 さらに節約を積み上げる
続けるか、$t_4 \to t_1$ でルートを閉じる ここで辺交換を確定

ここで重要なのは**「節約 − 消費」の累計(ゲイン)が正を保てる限り、連鎖を続ける**という点です。ゲインが負になりそうな方向には進まないので、無駄な探索を大幅に刈り込めます。

ゲインの計算

各ステップでのゲイン $G_i$ は次のように定義されます。

G_i = G_{i-1} + \underbrace{|e_i|}_{\text{削除した辺}} - \underbrace{|x_i|}_{\text{追加した辺}}
  • $G_i > 0$:この時点で閉じれば改善になる(採用候補)
  • $G_i \leq 0$:ここで打ち切り(これ以上続けても回収できない)

最終的に「閉じる辺を追加したときの $G_i$ が最大」となるタイミングを採用します。

2-opt・3-optとの関係

手法 連鎖の長さ 探索方法
2-opt 常に 1(辺を 2 本交換) 全組み合わせを試す
3-opt 常に 2(辺を 3 本交換) 全組み合わせを試す
LK法 動的(ゲインが正の限り延長) ゲインが正の方向だけを辿る

2-opt・3-opt が「全通り試して一番いい交換を選ぶ」のに対し、LK法は ゲインが正の経路だけを辿ってどんどん深掘りするイメージです。「全通り試さなくても高精度な改善が見つかる」ところが最大の特徴かと思います。

Python実装(簡略版)

完全なLK法の実装は複雑なため、ここでは核心となる「連鎖的な辺交換」の考え方を示すシンプル版を実装します。

import numpy as np

def lk_move(route, dist, t1_idx):
    """
    LK法の1ステップ:t1から始まる改善を探索
    route: 現在のルート
    dist: 距離行列
    t1_idx: 起点都市のインデックス
    """
    n = len(route)
    t1 = route[t1_idx]

    # t1に隣接する2辺を候補に(ルート上の前後)
    for direction in [-1, 1]:
        t2_idx = (t1_idx + direction) % n
        t2 = route[t2_idx]

        # 削除する辺 e1 = (t1, t2)
        g1_base = dist[t1][t2]

        # t2から到達できる候補都市 t3 を探索
        for t3_idx in range(n):
            t3 = route[t3_idx]
            if t3 == t1 or t3 == t2:
                continue

            # 追加する辺 x1 = (t2, t3) のコスト
            x1_cost = dist[t2][t3]
            g1 = g1_base - x1_cost

            # ゲインがマイナスなら打ち切り
            if g1 <= 0:
                continue

            # t3 に隣接する辺 e2 = (t3, t4) を削除候補として
            for direction2 in [-1, 1]:
                t4_idx = (t3_idx + direction2) % n
                t4 = route[t4_idx]
                if t4 == t1 or t4 == t2:
                    continue

                # 閉じる辺 (t4, t1) を追加したときの総ゲイン
                close_cost = dist[t4][t1]
                g2 = g1 + dist[t3][t4] - close_cost

                if g2 > 1e-10:
                    # 改善が見つかったので2-optムーブとして適用
                    i = min(t1_idx, t3_idx)
                    j = max(t1_idx, t3_idx)
                    new_route = route[:i + 1] + route[i + 1:j + 1][::-1] + route[j + 1:]
                    return new_route, g2

    return None, 0


def lin_kernighan(route, dist, max_iter=1000):
    """
    Lin-Kernighan法(簡略版)
    """
    n = len(route)
    best = route[:]
    best_dist = sum(dist[best[i]][best[(i + 1) % n]] for i in range(n))

    for _ in range(max_iter):
        improved = False
        for t1_idx in range(n):
            new_route, gain = lk_move(best, dist, t1_idx)
            if new_route is not None and gain > 1e-10:
                best = new_route
                best_dist -= gain
                improved = True
                break
        if not improved:
            break

    return best

実行してみる

def generate_cities(n=20, seed=42):
    np.random.seed(seed)
    return np.random.rand(n, 2) * 100

def calc_distance(cities):
    n = len(cities)
    dist = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            dist[i][j] = np.linalg.norm(cities[i] - cities[j])
    return dist

def total_distance(route, dist):
    n = len(route)
    return sum(dist[route[i]][route[(i + 1) % n]] for i in range(n))

def nearest_neighbor(dist, start=0):
    n = len(dist)
    unvisited = set(range(n))
    route = [start]
    unvisited.remove(start)
    while unvisited:
        current = route[-1]
        nearest = min(unvisited, key=lambda city: dist[current][city])
        route.append(nearest)
        unvisited.remove(nearest)
    return route

def two_opt(route, dist):
    n = len(route)
    best_route = route[:]
    improved = True
    while improved:
        improved = False
        for i in range(1, n - 1):
            for j in range(i + 1, n):
                a, b = best_route[i - 1], best_route[i]
                c, d = best_route[j], best_route[(j + 1) % n]
                if dist[a][c] + dist[b][d] < dist[a][b] + dist[c][d] - 1e-10:
                    best_route[i:j + 1] = best_route[i:j + 1][::-1]
                    improved = True
    return best_route

cities = generate_cities(n=20, seed=42)
dist   = calc_distance(cities)

nn_route   = nearest_neighbor(dist, start=7)
opt2_route = two_opt(nn_route, dist)
lk_route   = lin_kernighan(opt2_route, dist)

print(f'近傍法:    {total_distance(nn_route, dist):.1f}')
print(f'2-opt後:   {total_distance(opt2_route, dist):.1f}')
print(f'LK法後:    {total_distance(lk_route, dist):.1f}')

実用上のLK法:LKH

実際の研究・実務では Keld Helsgott による LKH(Lin-Kernighan-Helsgott) というLK法の改良版がデファクトスタンダードとして広く使われています。

LKHでは以下のような工夫が加えられています。

  • 候補辺リスト:全辺を試すのでなく、近くの辺だけを候補に絞ることで高速化
  • バックトラッキング:ゲインが小さくなっても、より深い改善を探索する
  • 5-opt以上のムーブ:より長い連鎖を試すことで高精度化

LKHはC言語で実装されており、公開されています。Pythonから呼び出すラッパーライブラリも存在します。

# pip install lkh でインストール可能(LKHバイナリが必要)
import lkh

# 問題をTSPLIB形式で渡す
# solver = lkh.solve('problem.tsp')

手法の比較まとめ

手法 交換する辺の数 計算量(1スキャン) 解の質
2-opt 2 $O(n^2)$ 普通
3-opt 3 $O(n^3)$ やや高い
LK法 可変(2〜k) $O(n^2)$ 〜 高い
LKH 可変+工夫 実用的 非常に高い

まとめ

今回はLin-Kernighan法を説明しました。

  • LK法:連鎖的な辺の追加・削除を動的な長さで行う高精度局所探索
  • 2-opt・3-optの一般化として位置付けられる
  • ゲイン(削除コスト - 追加コスト)が累積してプラスになる限り探索を続ける
  • 実用上は改良版の LKH が広く使われている

次回は局所最適から「確率的に脱出する」焼きなまし法(SA) を説明します。

参考にした記事や本、論文等

  • S. Lin and B. Kernighan, "An Effective Heuristic Algorithm for the Traveling-Salesman Problem," Operations Research, vol. 21, no. 2, pp. 498-516, 1973.
  • K. Helsgott, "An Effective Implementation of the Lin-Kernighan Traveling Salesman Heuristic," European Journal of Operational Research, vol. 126, pp. 106-130, 2000.
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?