4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事について

前回の記事ではTSP(巡回セールスマン問題)の基礎概念と、アプローチの全体像を説明しました。
今回はその第一歩として、近傍法(Nearest Neighbor Heuristic) を説明します。

「まず動くルートを素早く作る」ための構築ヒューリスティクスです。シンプルですが、後続の改善手法の出発点になる重要なアルゴリズムです。

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

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

近傍法とは?

近傍法(Nearest Neighbor Heuristic)は、「今いる都市から最も近い未訪問の都市へ移動する」 という単純なルールでルートを構築するアルゴリズムです。

人間が地図を見ながら「とりあえず近いところから回ろう」と考えるのとほぼ同じ発想です。

アルゴリズムの手順

① 出発都市をランダム(または固定)に選ぶ
② 現在地から最も近い未訪問の都市へ移動する
③ すべての都市を訪問するまで②を繰り返す
④ 最後に出発地へ戻る

具体例

5都市の例で考えてみましょう。都市0からスタートするとします。

都市0 → 最近傍=都市3 → 最近傍=都市1 → 最近傍=都市4 → 最近傍=都市2 → 都市0に戻る

このように、訪問済みの都市を除きながら「次に近い都市」を貪欲に選んでいきます。

Python実装

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Yu Gothic'

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):
            diff = cities[i] - cities[j]
            dist[i][j] = np.sqrt(np.sum(diff ** 2))
    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):
    """
    近傍法によるTSP解の構築
    dist: 距離行列
    start: 出発都市のインデックス
    """
    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

実行してみる

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

# 全都市をスタート地点として試し、最良のものを選ぶ
best_route = None
best_dist  = float('inf')

for start in range(len(cities)):
    route = nearest_neighbor(dist, start=start)
    d     = total_distance(route, dist)
    if d < best_dist:
        best_dist  = d
        best_route = route

print(f'近傍法の総距離: {best_dist:.1f}')
print(f'最良スタート都市: {best_route[0]}')
近傍法の総距離: 603.2
最良スタート都市: 7

可視化

def plot_route(cities, route, title='Route'):
    plt.figure(figsize=(7, 6))
    plt.scatter(cities[:, 0], cities[:, 1], c='red', s=80, zorder=5)
    for i, (x, y) in enumerate(cities):
        plt.annotate(str(i), (x + 1.5, y + 1.5), fontsize=8)
    for i in range(len(route)):
        c1 = cities[route[i]]
        c2 = cities[route[(i + 1) % len(route)]]
        plt.plot([c1[0], c2[0]], [c1[1], c2[1]], 'b-', linewidth=1.2, alpha=0.7)
    d = total_distance(route, calc_distance(cities))
    plt.title(f'{title}  (総距離: {d:.1f})')
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig('route_nn.png', dpi=150)
    plt.show()

plot_route(cities, best_route, title='近傍法')

近傍法の長所と短所

長所

  • 実装がシンプル:数十行で書ける
  • 高速:計算量は $O(n^2)$ で大規模な問題にも使いやすい
  • 直感的に理解しやすい:「近いところから回る」だけ

短所

  • 局所的な選択のため大域的に最悪になることがある:目先の最短を選び続けると、最後の方で遠回りになりやすい
  • スタート都市によって結果が変わる:スタートを変えて複数試すのが有効

近傍法が失敗するケース

近傍法の弱点として「最後の戻りで長い辺が生まれる」問題があります。

この問題を解消するのが、次回から説明する改善ヒューリスティクスです。

スタート都市の工夫

全スタート都市を試すのが最も確実ですが、都市数が多い場合はコストがかかります。
代わりに「最も外側の都市(凸包の頂点)からスタートする」という工夫もよく使われます。

まとめ

今回は近傍法を説明しました。

  • 近傍法:今いる都市から最も近い未訪問都市へ移動し続ける構築ヒューリスティクス
  • シンプルで高速だが、局所的な選択で全体が最悪になりやすい
  • スタート都市を複数試すことで改善できる

次回は近傍法で作ったルートをさらに改善する2-optを説明します。

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

  • D. S. Johnson and L. A. McGeoch, "The Traveling Salesman Problem: A Case Study in Local Optimization," in Local Search in Combinatorial Optimization, pp. 215-310, 1997.
4
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?