Google Summer of Code Logo with NetworkX logo

应该是我关于 Held-Karp 松弛的最后一篇博文!自从我上次发布标题为实现 Held-Karp 松弛的文章以来,我一直都在测试上升法和分支定界法。

我的第一个测试是使用一个真正的非对称图,而不是一个方向图,其中每个方向的成本恰好相同。为了创建这样的测试,我需要知道任何此类提议图的解决方案。我编写了一个名为brute_force_optimal_tour.py的 Python 脚本,它将生成一个随机图,打印其邻接矩阵,然后检查每种可能的边组合以找到最佳路径。

import networkx as nx
from itertools import combinations
import numpy as np
import math
import random


def is_1_arborescence(G):
    """
    Returns true if `G` is a 1-arborescence
    """
    return (
        G.number_of_edges() == G.order()
        and max(d for n, d in G.in_degree()) <= 1
        and nx.is_weakly_connected(G)
    )


# Generate a random adjacency matrix
size = (7, 7)
G_array = np.empty(size, dtype=int)
random.seed()
for r in range(size[0]):
    for c in range(size[1]):
        if r == c:
            G_array[r][c] = 0
            continue
        G_array[r][c] = random.randint(1, 100)

# Print that adjacency matrix
print(G_array)

G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
num_nodes = G.order()

combo_count = 0
min_weight_tour = None
min_tour_weight = math.inf
test_combo = nx.DiGraph()
for combo in combinations(G.edges(data="weight"), G.order()):
    combo_count += 1
    test_combo.clear()
    test_combo.add_weighted_edges_from(combo)
    # Test to see if test_combo is a tour.
    # This means first that it is an 1-arborescence
    if not is_1_arborescence(test_combo):
        continue
    # It also means that every vertex has a degree of 2
    arborescence_weight = test_combo.size("weight")
    if (
        len([n for n, deg in test_combo.degree if deg == 2]) == num_nodes
        and arborescence_weight < min_tour_weight
    ):
        # Tour found
        min_weight_tour = test_combo.copy()
        min_tour_weight = arborescence_weight

print(
    f"Minimum tour found with weight {min_tour_weight} from {combo_count} combinations of edges\n"
)
for u, v, d in min_weight_tour.edges(data="weight"):
    print(f"({u}, {v}, {d})")

上升法一切正常#

这是一个有用的信息,因为即使上升法返回一个向量,如果上升法返回此解决方案(即 $f(\pi) = 0$),我们可以根据解决方案中的边计算该向量,而无需显式枚举held_karp_ascent()返回的字典。

程序的第一个输出是一个六顶点图,如下所示。

~ time python3 brute_force_optimal_tour.py
[[ 0 45 39 92 29 31]
 [72  0  4 12 21 60]
 [81  6  0 98 70 53]
 [49 71 59  0 98 94]
 [74 95 24 43  0 47]
 [56 43  3 65 22  0]]
Minimum tour found with weight 144.0 from 593775 combinations of edges

(0, 5, 31)
(5, 4, 22)
(1, 3, 12)
(3, 0, 49)
(2, 1, 6)
(4, 2, 24)

real	0m9.596s
user	0m9.689s
sys     0m0.241s

首先,我检查了上升法是否返回了权重相同的解决方案 144,结果是它返回了。此外,向量中的每个条目都是 $0.866\overline{6}$,即 $\frac{5}{6}$ 或 Asadpour 论文中的缩放因子,所以我知道它找到了精确的解决方案。因此,我在test_traveling_salesman.py中的测试检查了对于解决方案边集中的所有边,$(u, v)$ 和 $(v, u)$ 是否都等于 $\frac{5}{6}$。

在我的下一个测试中,我创建了一个 $7 \times 7$ 矩阵进行测试,正如预期的那样,Python 脚本的运行时间慢得多。

~ time python3 brute_force_optimal_tour.py
[[ 0 26 63 59 69 31 41]
 [62  0 91 53 75 87 47]
 [47 82  0 90 15  9 18]
 [68 19  5  0 58 34 93]
 [11 58 53 55  0 61 79]
 [88 75 13 76 98  0 40]
 [41 61 55 88 46 45  0]]
Minimum tour found with weight 190.0 from 26978328 combinations of edges

(0, 1, 26)
(1, 3, 53)
(3, 2, 5)
(2, 5, 9)
(5, 6, 40)
(4, 0, 11)
(6, 4, 46)

real	7m28.979s
user	7m29.048s
sys     0m0.245s

再次,$f(\pi)$ 的值达到 0,因此上升法返回了一个精确的解决方案,我的测试程序与六顶点图的相同。

分支定界法的麻烦#

分支定界法在生成的两个示例图中效果不佳。首先,在七顶点矩阵上,我编写了测试并让它运行……运行……运行……直到我在运行超过一小时后停止它。如果它用八分之一的时间来蛮力计算解决方案,那么分支定界法确实效率低下。

我怀着希望转向了六顶点图,我已经有一个六顶点图,它在合理的时间内正确执行。当我运行测试时,六顶点图生成了大量异常和错误。我能够确定错误产生的原因,但上下文与我对分支定界法的预期不符。

基本上,direction_of_ascent_kilter() 找到一个超出平衡的顶点并返回相应的上升方向,但find_epsilon() 找不到任何有效的交叉边并返回最大移动方向 $\infty$。虽然我可以将find_epsilon()的返回值的默认值更改为零,但这并不能解决问题,因为向量 $\pi$ 的值将卡住,程序将进入无限循环。

我对此情况有一个类比。想象一下,你身处一个陌生的城市,必须在该城市最高的建筑物与某人会面。但是,你不知道地址,也没有办法获取到该建筑物的 GPS 路线。与其漫无目的地四处游荡,你决定扫描天际线以找到你能看到的最高的建筑物,然后开始沿着最接近该方向的街道行走。此外,你能够在任何给定的方向上判断在重新评估并选择新街道之前,沿着所选街道可以走多远。

这个假设更接近于上升法的近似,但这里的问题仍然可以证明。

  • 确定你是否在最高的建筑物上,相当于运行线性规划以查看上升方向是否存在。
  • 选择要走的街道,相当于找到上升方向。
  • 找出沿着那条街道走多远,相当于找到 epsilon。

经过一段时间后,你突然发现自己处于一种不寻常的情况。你仍然可以看到最高的建筑物,所以你知道你还没有到达那里。你知道哪条街道会带你更靠近建筑物,但由于某种原因,你无法沿着那条街道移动。

根据我对上升法和分支定界法的理解,如果上升方向存在,那么我们必须能够在该方向上移动一定的距离,并且不能失败,但分支定界法未能提供足够的移动距离。

考虑到分支定界法的问题,以及它不会在最终的 Asadpour 算法中使用,我计划将其从 NetworkX 的 pull request 中移除,并在其余的上升法中仅使用上升法继续前进。

参考文献#

A. Asadpour、M. X. Goemans、A. Mardry、S. O. Ghran 和 A. Saberi,《非对称旅行商问题的 o(log n / log log n) 近似算法》,运筹学,65 (2017),第 1043-1061 页。

M. Held、R. M. Karp,《旅行商问题和最小生成树》。运筹学,1970 年 11 月 1 日,第 18 卷(6),第 1138-1162 页。https://www.jstor.org/stable/169411