7.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()本节小结
这一节解决了什么问题?
如何证明贪心算法的正确性?
核心方法是什么?
交换论证法:把最优解换成贪心选择,证明不更差。
它为什么正确?
贪心选择性质 + 最优子结构 → 贪心有效。
它在什么情况下不适用?
当问题缺乏最优子结构或贪心选择性质时。
习题
- 对 Huffman 编码进行交换论证。
- 证明分数背包问题满足贪心选择性质。
- 给出一个问题,它满足最优子结构但不满足贪心选择性质。