基于线性规划的图Steiner树问题的原始-对偶近似算法求解示例
字数 4556 2025-12-15 23:52:04

基于线性规划的图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 树)。具体步骤如下:

  1. 初始化

    • 对偶变量 \(y_S \leftarrow 0\) 对所有满足条件的 \(S\)
    • 原始解 \(F \leftarrow \emptyset\)(最终选取的边集)。
    • 每个终端结点初始为一个“活动组件”(component),组件的结点集就是该终端结点自身。
  2. 迭代增长对偶变量

    • 只要存在活动组件(即包含终端结点且尚未连接到其他终端组件的连通块),就同时增加所有活动组件对应的对偶变量 \(y_C\)(这里 \(C\) 表示当前活动组件的结点集)。
    • 增加过程中,当某条边 \(e\) 的对偶约束变紧(即 \(\sum_{S: e \in \delta(S)} y_S = c_e\)),则将边 \(e\) 加入一个临时集合 \(TightEdges\),但暂时不加入 \(F\)
  3. 合并组件

    • 当边 \(e\) 连接两个不同的活动组件时,该边会使得两个组件合并。此时,将 \(e\) 加入 \(F\),并将这两个活动组件合并为一个新的组件,新组件仍然是活动的(因为它包含终端结点,除非它包含了所有终端结点)。
    • 重复步骤2-3,直到所有终端结点都在同一个组件中(即 Steiner 树已连通所有终端)。
  4. 剪枝冗余边

    • 最后得到的 \(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. 初始活动组件:{1}, {3}, {5},分别对应对偶变量 \(y_{\{1\}}, y_{\{3\}}, y_{\{5\}}\)
  2. 同时增加这三个对偶变量。当增加到 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(非终端,属于一个新组件,目前单独一个结点不算活动组件,暂时不合并)。我们继续增加对偶变量,同时观察其他边。
  3. 实际上,标准算法中,只有当一条边连接两个不同的活动组件时才加入 \(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}。
  4. 现在活动组件为{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,合并所有终端。
  5. 此时 \(F = \{3-4, 4-5, 1-5\}\)。总费用=1+2+4=7。
  6. 剪枝:检查叶子结点,结点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树问题的整数规划建模、线性规划松弛、对偶规划,并给出了一个原始-对偶近似算法的详细步骤,通过一个小例子演示了算法过程,并提到了算法的近似比保证。这个算法可以在多项式时间内运行,并得到质量有保证的近似解。

基于线性规划的图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(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\}。 现在活动组件为\{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,合并所有终端。 此时 \(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树问题的整数规划建模、线性规划松弛、对偶规划,并给出了一个原始-对偶近似算法的详细步骤,通过一个小例子演示了算法过程,并提到了算法的近似比保证。这个算法可以在多项式时间内运行,并得到质量有保证的近似解。