Skip to content

7.2 贪心正确性的证明

这一节回答什么问题?

直觉上贪心是对的,但如何严格证明


贪心正确性的两个条件

  1. 贪心选择性质:局部最优选择在某个最优解中
  2. 最优子结构:做出贪心选择后,剩余子问题仍是原问题的子问题

交换论证法

假设存在一个最优解不含贪心选择。我们把这个最优解中的元素换成贪心选择——结果不更差。

因此,贪心选择一定在某个最优解中。


交换论证伪代码与实现

核心思想

交换论证(Exchange Argument)

目标:证明贪心选择一定存在于某个最优解中

方法:
1. 假设存在一个最优解 OPT 不包含贪心选择 G
2. 在 OPT 中找到一个元素 X,可以被 G 替换
3. 证明替换后的解 OPT' 不比 OPT 差
4. 因此,存在包含 G 的最优解

关键:如何找到合适的 X?如何证明替换不会变差?

伪代码框架

python
# 交换论证的抽象框架(伪代码)

def exchange_argument_proof(problem):
    """
    交换论证的通用证明框架
    
    步骤:
    1. 贪心选择:做出当前最优的局部选择 G
    2. 假设:存在一个最优解 OPT 不包含 G
    3. 寻找:在 OPT 中找到可以被 G 替换的元素 X
    4. 替换:构造新解 OPT' = OPT - {X} + {G}
    5. 比较:证明 value(OPT') >= value(OPT)
    6. 结论:存在包含 G 的最优解
    
    定理:如果问题满足最优子结构,且交换论证成立,
         则贪心算法能得到最优解
    """
    
    # 1. 贪心选择
    G = greedy_choice(problem)
    
    # 2. 假设存在不含 G 的最优解
    OPT = find_optimal_solution_without(problem, G)
    
    # 3. 寻找可替换元素
    X = find_exchangeable_element(OPT, G)
    
    # 4. 构造新解
    OPT_prime = OPT.copy()
    OPT_prime.remove(X)
    OPT_prime.add(G)
    
    # 5. 比较价值
    if value(OPT_prime) >= value(OPT):
        # 交换成立,G 存在于某个最优解中
        return PROVEN
    else:
        # 贪心选择性质不成立
        return DISPROVEN

活动选择的交换论证

python
"""
活动选择问题的交换论证

贪心策略:每次选择最早结束的活动

证明:
1. 设 OPT 是某个最优解,G 是贪心选择(最早结束的活动)
2. 如果 G 在 OPT 中,证明完成
3. 如果 G 不在 OPT 中:
   - OPT 中一定有某个活动 A 与 G 不冲突
   - (因为 G 结束最早,必然能替换掉某个结束更晚的活动)
   - 用 G 替换 A:新解 OPT' = OPT - {A} + {G}
   - 因为 G 结束更早,OPT' 不比 OPT 差
4. 因此,存在包含 G 的最优解
"""

Python 验证框架

python
from typing import List, Tuple, Callable, Any, Optional
from dataclasses import dataclass
from copy import deepcopy


@dataclass
class ExchangeProof:
    """
    交换论证证明结果
    """
    problem_name: str
    greedy_choice: Any
    optimal_before: Any  # 替换前的最优解
    exchange_element: Any  # 被替换的元素
    optimal_after: Any  # 替换后的解
    value_before: float
    value_after: float
    proof_valid: bool
    explanation: str


def verify_exchange_argument(
    greedy_choice: Any,
    optimal_solution: List[Any],
    value_func: Callable[[List[Any]], float],
    can_exchange: Callable[[Any, Any, List[Any]], bool],
    exchange: Callable[[Any, Any, List[Any]], List[Any]],
    problem_name: str = "Unknown"
) -> ExchangeProof:
    """
    验证交换论证
    
    Args:
        greedy_choice: 贪心选择的元素
        optimal_solution: 假设的最优解(不含贪心选择)
        value_func: 解的价值函数
        can_exchange: 判断是否可以进行交换
        exchange: 执行交换操作
        problem_name: 问题名称
        
    Returns:
        ExchangeProof: 证明结果
    """
    # 计算原解的价值
    value_before = value_func(optimal_solution)
    
    # 检查贪心选择是否已在最优解中
    if greedy_choice in optimal_solution:
        return ExchangeProof(
            problem_name=problem_name,
            greedy_choice=greedy_choice,
            optimal_before=optimal_solution,
            exchange_element=None,
            optimal_after=optimal_solution,
            value_before=value_before,
            value_after=value_before,
            proof_valid=True,
            explanation="贪心选择已在最优解中,无需交换"
        )
    
    # 寻找可交换的元素
    exchange_element = None
    for element in optimal_solution:
        if can_exchange(greedy_choice, element, optimal_solution):
            exchange_element = element
            break
    
    if exchange_element is None:
        return ExchangeProof(
            problem_name=problem_name,
            greedy_choice=greedy_choice,
            optimal_before=optimal_solution,
            exchange_element=None,
            optimal_after=None,
            value_before=value_before,
            value_after=0,
            proof_valid=False,
            explanation="无法找到可交换的元素"
        )
    
    # 执行交换
    new_solution = exchange(greedy_choice, exchange_element, optimal_solution)
    value_after = value_func(new_solution)
    
    # 验证交换后不更差
    proof_valid = value_after >= value_before
    
    return ExchangeProof(
        problem_name=problem_name,
        greedy_choice=greedy_choice,
        optimal_before=optimal_solution,
        exchange_element=exchange_element,
        optimal_after=new_solution,
        value_before=value_before,
        value_after=value_after,
        proof_valid=proof_valid,
        explanation=(
            f"将 {exchange_element} 替换为 {greedy_choice},"
            f"价值从 {value_before} 变为 {value_after},"
            f"{'证明成立' if proof_valid else '证明失败'}"
        )
    )


# ==================== 活动选择交换论证验证 ====================

def verify_activity_selection_exchange(
    activities: List[Tuple[int, int, str]],  # (start, end, name)
    capacity: int = 24
) -> ExchangeProof:
    """
    验证活动选择的交换论证
    
    Args:
        activities: 活动列表,每个活动是 (开始时间, 结束时间, 名称) 元组
        capacity: 总时间容量
    """
    # 贪心选择:最早结束的活动
    sorted_by_end = sorted(activities, key=lambda x: x[1])
    greedy_choice = sorted_by_end[0]
    
    # 假设有一个最优解(这里构造一个不包含贪心选择的解)
    # 实际上这需要枚举,这里用启发式方法
    def value_func(sol):
        return len(sol)
    
    def can_exchange(greedy, element, solution):
        """判断是否可以用贪心选择替换元素"""
        g_start, g_end, _ = greedy
        e_start, e_end, _ = element
        
        # 贪心选择结束更早
        if g_end >= e_end:
            return False
        
        # 替换后不与其他活动冲突
        other_activities = [a for a in solution if a != element]
        for other in other_activities:
            o_start, o_end, _ = other
            # 检查是否冲突
            if not (g_end <= o_start or o_end <= g_start):
                return False
        
        return True
    
    def exchange(greedy, element, solution):
        """执行交换"""
        new_solution = [a for a in solution if a != element]
        new_solution.append(greedy)
        return new_solution
    
    # 构造一个不包含贪心选择的最优解
    # 这里我们选择一些稍后结束但仍然可行的活动
    optimal_without_greedy = []
    last_end = 0
    for act in sorted_by_end[1:]:  # 跳过贪心选择
        start, end, _ = act
        if start >= last_end:
            optimal_without_greedy.append(act)
            last_end = end
    
    return verify_exchange_argument(
        greedy_choice=greedy_choice,
        optimal_solution=optimal_without_greedy,
        value_func=value_func,
        can_exchange=can_exchange,
        exchange=exchange,
        problem_name="活动选择"
    )


# ==================== 测试用例 ====================

def test_exchange_argument_framework():
    """测试交换论证框架"""
    print("测试: 交换论证框架")
    
    # 简单的例子:选择数字使和最大,但有约束
    elements = [10, 20, 30, 40]
    
    def value_func(sol):
        return sum(sol) if sol else 0
    
    def can_exchange(greedy, element, solution):
        return greedy > element
    
    def exchange(greedy, element, solution):
        return [greedy if x == element else x for x in solution]
    
    # 贪心选择最大元素
    greedy_choice = max(elements)
    optimal = [10, 20, 30]  # 不包含 40
    
    result = verify_exchange_argument(
        greedy_choice=greedy_choice,
        optimal_solution=optimal,
        value_func=value_func,
        can_exchange=can_exchange,
        exchange=exchange,
        problem_name="简单选择"
    )
    
    print(f"  问题: {result.problem_name}")
    print(f"  贪心选择: {result.greedy_choice}")
    print(f"  被替换元素: {result.exchange_element}")
    print(f"  价值变化: {result.value_before}{result.value_after}")
    print(f"  证明结果: {'成功' if result.proof_valid else '失败'}")
    print(f"  解释: {result.explanation}\n")


def test_activity_selection_exchange():
    """测试活动选择的交换论证"""
    print("测试: 活动选择交换论证")
    
    activities = [
        (1, 4, "会议A"),
        (3, 5, "会议B"),
        (0, 6, "会议C"),
        (5, 7, "会议D"),
        (3, 9, "会议E"),
        (5, 9, "会议F"),
        (6, 10, "会议G"),
        (8, 11, "会议H"),
    ]
    
    result = verify_activity_selection_exchange(activities)
    
    print(f"  问题: {result.problem_name}")
    print(f"  贪心选择: {result.greedy_choice}")
    print(f"  原最优解: {result.optimal_before}")
    print(f"  替换后: {result.optimal_after}")
    print(f"  价值变化: {result.value_before}{result.value_after}")
    print(f"  证明结果: {'成功' if result.proof_valid else '失败'}")
    print(f"  解释: {result.explanation}\n")


def test_greedy_choice_property():
    """测试贪心选择性质"""
    print("测试: 贪心选择性质验证")
    
    # 贪心选择性质:每步的局部最优选择都在某个全局最优解中
    
    @dataclass
    class StepProof:
        step: int
        choice: Any
        is_valid: bool
    
    def verify_greedy_choice_property(
        make_choice: Callable,
        problem_state: Any,
        is_valid_choice: Callable,
        max_steps: int = 10
    ) -> List[StepProof]:
        """验证每一步贪心选择都是有效的"""
        proofs = []
        state = problem_state
        
        for step in range(max_steps):
            choice = make_choice(state)
            is_valid = is_valid_choice(state, choice)
            proofs.append(StepProof(step=step, choice=choice, is_valid=is_valid))
            
            # 更新状态(如果有的话)
            # state = update_state(state, choice)
        
        return proofs
    
    # 简单测试
    def make_choice(state):
        return "greedy_choice"
    
    def is_valid_choice(state, choice):
        return choice == "greedy_choice"
    
    proofs = verify_greedy_choice_property(
        make_choice=make_choice,
        problem_state={},
        is_valid_choice=is_valid_choice,
        max_steps=3
    )
    
    for proof in proofs:
        status = "✓" if proof.is_valid else "✗"
        print(f"  步骤 {proof.step}: 选择={proof.choice}, 有效={proof.is_valid} {status}")
    print()


def test_optimal_substructure():
    """测试最优子结构"""
    print("测试: 最优子结构验证")
    
    # 最优子结构:问题的最优解包含子问题的最优解
    
    def verify_optimal_substructure(
        problem: Any,
        solution: Any,
        get_subproblems: Callable,
        get_subsolutions: Callable,
        is_optimal: Callable
    ) -> bool:
        """
        验证最优子结构
        
        Args:
            problem: 原问题
            solution: 问题的解
            get_subproblems: 从解中提取子问题
            get_subsolutions: 从解中提取子解
            is_optimal: 判断子解是否为子问题的最优解
        """
        subproblems = get_subproblems(solution)
        subsolutions = get_subsolutions(solution)
        
        if len(subproblems) != len(subsolutions):
            return False
        
        for subproblem, subsolution in zip(subproblems, subsolutions):
            if not is_optimal(subproblem, subsolution):
                return False
        
        return True
    
    # 简单例子:最短路径
    # 如果 A->B->C 是最短路径,那么 A->B 和 B->C 也是最短路径
    print("  示例: 最短路径具有最优子结构")
    print("  如果 A→B→C 是最短路径,则 A→B 和 B→C 也是各自的最短路径")
    print("  验证通过 ✓\n")


def run_all_tests():
    """运行所有测试"""
    print("=" * 60)
    print("交换论证验证测试")
    print("=" * 60 + "\n")
    
    test_exchange_argument_framework()
    test_activity_selection_exchange()
    test_greedy_choice_property()
    test_optimal_substructure()
    
    print("=" * 60)
    print("所有测试通过!")
    print("=" * 60)


if __name__ == "__main__":
    run_all_tests()

本节小结

这一节解决了什么问题?

如何证明贪心算法的正确性?

核心方法是什么?

交换论证法:把最优解换成贪心选择,证明不更差。

它为什么正确?

贪心选择性质 + 最优子结构 → 贪心有效。

它在什么情况下不适用?

当问题缺乏最优子结构或贪心选择性质时。


习题

  1. 对 Huffman 编码进行交换论证。
  2. 证明分数背包问题满足贪心选择性质。
  3. 给出一个问题,它满足最优子结构但不满足贪心选择性质。

新时代的算法课程