基于线性规划的图Steiner树问题的原始-对偶近似算法求解示例
题目描述
考虑一个无向图 \(G = (V, E)\),其中每条边 \(e \in E\) 具有非负费用 \(c_e \ge 0\)。给定一个终端结点集合 \(T \subseteq V\)(\(T\) 非空),目标是找到一个连通子图 \(H \subseteq G\),使得 \(H\) 包含 \(T\) 中的所有终端结点(可以包含非终端结点,称为 Steiner 结点),并且使得 \(H\) 中所有边的总费用最小。这个最小费用连通子图称为 Steiner 树。本问题是 NP-难问题,我们将设计一个基于线性规划松弛的原始-对偶近似算法,在多项式时间内给出一个近似解,并分析其近似比。
解题过程
我们将循序渐进地讲解如何构建整数规划模型、其线性规划松弛、对偶线性规划,然后设计原始-对偶近似算法,并通过一个具体例子演示算法步骤。
步骤1:建立整数规划模型
我们引入变量 \(x_e \in \{0,1\}\) 表示边 \(e\) 是否被选入 Steiner 树。对于任意结点集合 \(S \subseteq V\),用 \(\delta(S)\) 表示一个端点在 \(S\) 内、另一个端点在 \(S\) 外的边集。为了保证所有终端结点连通,必须要求:对于任意满足 \(S \cap T \ne \emptyset\) 且 \(T \setminus S \ne \emptyset\) 的结点集 \(S \subset V\)(即 \(S\) 包含部分终端结点但不包含所有终端结点),至少有一条被选边横跨 \(S\) 的边界(即连接 \(S\) 内部和外部的边)。于是得到整数规划:
\[\begin{aligned} \min \quad & \sum_{e \in E} c_e x_e \\ \text{s.t.} \quad & \sum_{e \in \delta(S)} x_e \ge 1, \quad \forall S \subset V, S \cap T \neq \emptyset, T \setminus S \neq \emptyset \\ & x_e \in \{0,1\}, \quad \forall e \in E \end{aligned} \]
约束条件称为“割条件”,确保任意将终端结点分开的割中至少有一条边被选。
步骤2:线性规划松弛及其对偶
将整数变量松弛为连续变量得到线性规划(原始线性规划):
\[\begin{aligned} \min \quad & \sum_{e \in E} c_e x_e \\ \text{s.t.} \quad & \sum_{e \in \delta(S)} x_e \ge 1, \quad \forall S \subset V, S \cap T \neq \emptyset, T \setminus S \neq \emptyset \\ & x_e \ge 0, \quad \forall e \in E \end{aligned} \]
对每个满足条件的集合 \(S\) 引入对偶变量 \(y_S \ge 0\),则对偶线性规划为:
\[\begin{aligned} \max \quad & \sum_{S} y_S \\ \text{s.t.} \quad & \sum_{S: e \in \delta(S)} y_S \le c_e, \quad \forall e \in E \\ & y_S \ge 0, \quad \forall S \subset V, S \cap T \neq \emptyset, T \setminus S \neq \emptyset \end{aligned} \]
对偶约束的意义是:每条边 \(e\) 所关联的所有割的对偶变量之和不超过该边的费用。
步骤3:原始-对偶近似算法设计
算法基本思想:从对偶可行解 \(y_S=0\) 出发,逐步增大某些对偶变量,使得某些边对应的对偶约束变紧(等号成立),然后选取这些边构造原始可行解(即 Steiner 树)。具体步骤如下:
-
初始化:
- 对偶变量 \(y_S \leftarrow 0\) 对所有满足条件的 \(S\)。
- 原始解 \(F \leftarrow \emptyset\)(最终选取的边集)。
- 每个终端结点初始为一个“活动组件”(component),组件的结点集就是该终端结点自身。
-
迭代增长对偶变量:
- 只要存在活动组件(即包含终端结点且尚未连接到其他终端组件的连通块),就同时增加所有活动组件对应的对偶变量 \(y_C\)(这里 \(C\) 表示当前活动组件的结点集)。
- 增加过程中,当某条边 \(e\) 的对偶约束变紧(即 \(\sum_{S: e \in \delta(S)} y_S = c_e\)),则将边 \(e\) 加入一个临时集合 \(TightEdges\),但暂时不加入 \(F\)。
-
合并组件:
- 当边 \(e\) 连接两个不同的活动组件时,该边会使得两个组件合并。此时,将 \(e\) 加入 \(F\),并将这两个活动组件合并为一个新的组件,新组件仍然是活动的(因为它包含终端结点,除非它包含了所有终端结点)。
- 重复步骤2-3,直到所有终端结点都在同一个组件中(即 Steiner 树已连通所有终端)。
-
剪枝冗余边:
- 最后得到的 \(F\) 可能包含一些非终端叶子结点(Steiner 结点)上的叶子边,这些边对连通终端不是必需的,可以从叶子到根进行后序遍历,删除那些不在任何终端之间关键路径上的叶子边(即删除那些以 Steiner 结点为叶子且该 Steiner 结点度数为1的边),得到最终的 Steiner 树 \(F'\)。
此算法是原始-对偶方法的一种实现,其近似比为 \(2(1 - 1/|T|)\),即最坏情况下解的费用不超过最优解的 \(2(1 - 1/|T|)\) 倍。
步骤4:实例演示
考虑下图(结点编号1-5,边费用如图):
- 结点集 \(V = \{1,2,3,4,5\}\)
- 终端集合 \(T = \{1,3,5\}\)
- 边及费用:\(e_{12}=2, e_{23}=3, e_{34}=1, e_{45}=2, e_{15}=4, e_{24}=2\)
1
/|\
2/ | \4
/ | \
2---3---5
3 2
\ /
4
1
(上图仅为示意,实际边为:1-2(2), 2-3(3), 3-4(1), 4-5(2), 1-5(4), 2-4(2),结点4为非终端结点)
算法执行:
- 初始活动组件:{1}, {3}, {5},分别对应对偶变量 \(y_{\{1\}}, y_{\{3\}}, y_{\{5\}}\)。
- 同时增加这三个对偶变量。当增加到 0.5 时,检查哪些边变紧:
- 边1-2:约束涉及组件{1}(因为1在组件内,2在外),对偶和=\(y_{\{1\}}=0.5<2\),未紧。
- 边3-4:约束涉及组件{3},对偶和=0.5<1,未紧。
- 继续增加...
为简化,我们手动模拟:当增加到1时,边3-4的费用为1,约束变紧(因为此时只有组件{3}贡献对偶值1)。但3-4连接组件{3}和结点4(非终端,属于一个新组件,目前单独一个结点不算活动组件,暂时不合并)。我们继续增加对偶变量,同时观察其他边。
- 实际上,标准算法中,只有当一条边连接两个不同的活动组件时才加入 \(F\)。我们持续增加对偶变量,直到某条边连接两个活动组件:
- 假设增加到 \(y_{\{1\}}=2, y_{\{3\}}=1, y_{\{5\}}=2\) 时:
- 边1-2:对偶和=\(y_{\{1\}}=2 = c_{12}=2\),变紧,连接组件{1}和结点2(非终端,暂不处理)。
- 边3-4:对偶和=\(y_{\{3\}}=1 = c_{34}=1\),变紧,连接组件{3}和结点4。
- 边4-5:对偶和=\(y_{\{5\}}=2 = c_{45}=2\),变紧,连接组件{5}和结点4。
现在,边3-4和4-5连接了组件{3}、结点4、组件{5}。注意结点4连接了两个不同活动组件{3}和{5},通过边3-4和4-5间接连接。算法中,当边连接两个不同活动组件时,将其加入 \(F\) 并合并组件。这里,边3-4连接组件{3}和结点4(非活动),边4-5连接结点4和组件{5},所以并未直接连接两个活动组件。但当我们加入边3-4后,组件{3}扩展为{3,4}(仍为活动,因为包含终端3),此时边4-5连接活动组件{3,4}和活动组件{5},满足条件,因此加入边4-5,合并为组件{3,4,5}。
- 假设增加到 \(y_{\{1\}}=2, y_{\{3\}}=1, y_{\{5\}}=2\) 时:
- 现在活动组件为{1}和{3,4,5}。继续增加它们的对偶变量:
- 增加到 \(y_{\{1\}}=2+1=3, y_{\{3,4,5\}}=0+?\),注意合并后新组件的对偶变量初始为0,但我们可以继续增加。实际上,更简单的方法是:当组件合并后,原来的对偶变量不再增加,而是开始增加新组件的对偶变量。为简化,我们直接寻找连接组件{1}和{3,4,5}的边:
候选边:1-2(费用2)已变紧,但结点2不在组件{3,4,5}中;边1-5(费用4)连接组件{1}和结点5(结点5在组件{3,4,5}中),但其对偶和=\(y_{\{1\}}+y_{\{3,4,5\}}\),若 \(y_{\{3,4,5\}}\) 增加到1,则和为4,等于费用,变紧。因此加入边1-5,合并所有终端。
- 增加到 \(y_{\{1\}}=2+1=3, y_{\{3,4,5\}}=0+?\),注意合并后新组件的对偶变量初始为0,但我们可以继续增加。实际上,更简单的方法是:当组件合并后,原来的对偶变量不再增加,而是开始增加新组件的对偶变量。为简化,我们直接寻找连接组件{1}和{3,4,5}的边:
- 此时 \(F = \{3-4, 4-5, 1-5\}\)。总费用=1+2+4=7。
- 剪枝:检查叶子结点,结点4是Steiner结点,度数为2(边3-4和4-5),不是叶子,无需剪枝。最终Steiner树为边集{1-5,3-4,4-5},总费用7。
注意:此例中,最优Steiner树可能是边{1-2,2-4,4-5,4-3},费用=2+2+2+1=7,或者{1-2,2-4,4-5,1-5}费用=2+2+2+4=10等。我们的解费用7等于最优解(可能不唯一),说明算法在此例给出了最优解。
步骤5:算法近似比分析
该算法的近似比为 \(2(1-1/|T|)\),推导基于对偶可行解和原始整数解之间的比较。简要思路:最终树中每条边被“支付”了对偶变量的和,每个割的对偶变量贡献最多被计算两次(因为树是连通的,每条边最多属于两个不同方向增长的组件)。详细证明需用到对偶互补松弛条件及树的结构性质,由于篇幅有限不再展开。
总结
我们介绍了Steiner树问题的整数规划建模、线性规划松弛、对偶规划,并给出了一个原始-对偶近似算法的详细步骤,通过一个小例子演示了算法过程,并提到了算法的近似比保证。这个算法可以在多项式时间内运行,并得到质量有保证的近似解。