CC++ & Algorithm

分治算法思想

中等6
语言版本:C++Python
概述:把大问题拆成小问题,解决小问题再合并结果,就像分小组完成任务一样简单。

分治算法:把大难题拆成小任务,轻松搞定!

你有没有遇到过这样的作业:老师让你统计全班50个同学的身高总和?如果你一个人拿着计算器一个一个加,很容易算错,而且手都酸了。但如果你让每个小组先算自己组的身高和,然后你只把几个小组的答案加起来,是不是又快又准?这就是分治算法的智慧——把一个大问题拆成几个小问题,解决小问题后再把结果合并起来。

分治算法是一种非常聪明的解决问题的方法。它就像你和小伙伴们一起打扫一个大教室:如果只有一个人,要扫全教室会很累,但如果你把教室分成四个区域,每个小组负责一块,扫完后再把垃圾集中到一起,是不是又快又轻松?分治算法就是这种“分而治之”的思想。


分治算法的三个步骤

分治算法通常分三步走,记住这个“三步曲”就能掌握它:

  1. 分解(Divide):把大问题拆成几个更小的子问题,直到子问题简单到可以直接解决。就像切蛋糕,一刀切两半,再切两半,直到每块都小到可以一口吃掉。
  2. 解决(Conquer):分别解决每个小问题。如果小问题还不够小,就继续拆。就像打扫卫生时,每个小组各自打扫自己的区域。
  3. 合并(Combine):把所有小问题的解合并起来,得到原问题的解。就像打扫完后,每个小组把垃圾倒到同一个大垃圾桶里。

这个三步曲就像搭积木:先把一大堆积木分成几小堆,分别搭好小房子,再把小房子拼成一个大城堡。


生活中的分治小例子

  • 老师让你计算全班同学的身高总和:如果一个人算要很久,你可以让同学们分小组计算本组的身高和,然后把各组的和加起来,就得到全班和。这里分组就是“分解”,每组算自己的和就是“解决”,把组和相加就是“合并”。

  • 整理书架:书架上的书乱七八糟,如果直接整理整个书架会头大。你可以把书架分成上下两层,每层再分成左右两半,分别整理好,最后组合起来。这就是分治!

  • 找东西:妈妈让你找掉在地上的橡皮,你从房间的一角开始,把房间分成左半和右半,先找左边,再找右边。如果左边还有大箱子,就再拆成箱子里面和外面……直到找到橡皮。

  • 全班排队报数:体育委员让全班同学按身高排队,如果让每个人和旁边的人比,太慢了。更好的方法是:先分成两组,每组内部排好,再把两组合并成一排(像归并排序那样)。


用编程来理解分治

下面我们用Python写一个函数,用分治思想计算一个列表中所有数字的和。如果列表只有一个数,直接返回;否则从中间切开,分别求和再相加。

def sum_list(arr):
    # 基准情况:如果列表为空,和为0
    if not arr:
        return 0
    # 基准情况:如果只有一个元素,直接返回
    if len(arr) == 1:
        return arr[0]
    # 分解:找到中间位置
    mid = len(arr) // 2
    # 递归解决:分别求左右两半的和
    left_sum = sum_list(arr[:mid])
    right_sum = sum_list(arr[mid:])
    # 合并:左右和相加
    return left_sum + right_sum

# 测试
numbers = [3, 8, 2, 5, 1, 9]
print("总和是:", sum_list(numbers))  # 输出: 28

这段代码中,函数sum_list不断将列表一分为二,直到只剩一个数字,然后一层层返回相加。比如列表[3, 8, 2, 5, 1, 9],先切分成[3, 8, 2][5, 1, 9],再继续切,直到每个子列表只有一个数,然后返回并加起来。这就是核心思想:大事化小,小事化了


小挑战:用分治求最大值

除了求和,求最大值也可以用分治。比如你有一堆考试分数,想知道最高分是多少。可以这样想:如果只有一个人,分数就是最高分;如果有两个人,比较一下谁高;如果有很多人,分成两半,分别找出两边的最高分,再比较哪个更高。

def max_number(arr):
    # 基准情况:如果只有一个数,它就是最大值
    if len(arr) == 1:
        return arr[0]
    # 基准情况:如果两个数,直接比较
    if len(arr) == 2:
        return arr[0] if arr[0] > arr[1] else arr[1]
    # 分解:从中间切
    mid = len(arr) // 2
    # 解决:分别找出左右的最大值
    left_max = max_number(arr[:mid])
    right_max = max_number(arr[mid:])
    # 合并:返回较大的那个
    return left_max if left_max > right_max else right_max

# 测试
scores = [88, 92, 76, 95, 84, 91]
print("最高分是:", max_number(scores))  # 输出: 95

新手容易犯的错误

虽然分治思想听起来简单,但写代码时容易掉进几个坑:

  1. 忘记基准情况(递归出口):如果不设置“什么时候不用再拆”,函数会无限递归,最后程序崩溃(栈溢出)。比如上面的求和函数,一定要有len(arr)==1arr为空时直接返回。没有基准情况,就像打扫卫生时一直分区域,最后每个区域只有一张纸,还在继续分,永远分不完。

  2. 分解时遗漏元素:用切片arr[:mid]arr[mid:]时,要注意中间元素有没有被漏掉。比如列表长度是奇数,中间元素会分到右边一半(因为mid是整数除法)。这是正确的,不会遗漏。

  3. 合并逻辑写错:比如求和时,把左右和相加是对的。但如果是求最大值,合并时要用max()而不是相加。很多人会把求和的思路硬套到别的场景上,导致结果奇怪。

  4. 认为所有问题都适合分治:分治适合可以分解成独立子问题的问题。如果子问题之间有重叠(比如斐波那契数列),用分治会重复计算很多次,这时候动态规划更合适。


完整可运行的示例:用分治计算一个班级的总分和平均分

假设班里同学的成绩单如下:[88, 92, 76, 95, 84, 91, 79, 85]。我们用分治求总分、平均分(平均分 = 总分 / 人数)。下面是一个完整的程序,包含main函数调用。

def total_sum(arr):
    """分治求列表总和"""
    # 基准情况:空列表和为0
    if not arr:
        return 0
    # 基准情况:只有一个元素,直接返回
    if len(arr) == 1:
        return arr[0]
    # 分解
    mid = len(arr) // 2
    # 解决(递归)
    left_sum = total_sum(arr[:mid])
    right_sum = total_sum(arr[mid:])
    # 合并
    return left_sum + right_sum

def average_score(arr):
    """用总分计算平均分"""
    total = total_sum(arr)
    return total / len(arr)

# 主程序
scores = [88, 92, 76, 95, 84, 91, 79, 85]
print("成绩列表:", scores)
print("总分:", total_sum(scores))
print("平均分:", average_score(scores))

运行结果:

成绩列表: [88, 92, 76, 95, 84, 91, 79, 85]
总分: 690
平均分: 86.25

这个程序把求总分分解成求左半和右半的和,最终得到准确答案。你也可以试着改成求最低分、求所有成绩的乘积等,都可以用同样的分治思路。


接下来可以学什么?

掌握了分治的思想,你就可以去挑战更厉害的算法了:

  • 归并排序:最经典的分治排序算法,把数组拆成两半分别排序,再合并。像全班同学先分成两组按身高排队,再合并成一排。
  • 快速排序:另一个分治排序,选一个“基准”,把小于基准和大于基准的分开,再分别排序。
  • 二分查找:在一个有序列表中找某个数,每次把范围缩小一半。这其实也是分治的简化版(不用合并结果)。
  • 卡塔兰数、汉诺塔:这些都是分治思想的经典应用。

分治算法的魅力在于:只要你会拆、会合并,再复杂的问题也能变成小菜一碟。下次遇到难题,不妨先想:能不能把它拆成几个小问题?试试看吧!

例题精讲

1单选题

分治算法通常包含哪三个步骤?

A分解、解决、合并
B分解、合并、解决
C解决、分解、合并
D合并、分解、解决
2单选题

以下哪个算法不是分治算法的典型应用?

A归并排序
B快速排序
C二分查找
D冒泡排序
3判断题

分治算法必须使用递归来实现。

4填空题
用分治算法计算列表arr的和。请补全代码。
def sum_list(arr):
    if len(arr) == 0:
        return 0
    if len(arr) == 1:
        return arr[0]
    mid = len(arr) // 2
    left_sum = sum_list(arr[:mid])
    right_sum = sum_list(arr[mid:])
    return ___
5填空题
用分治算法求列表arr中的最大值。请补全代码。
def find_max(arr):
    if len(arr) == 1:
        return arr[0]
    mid = len(arr) // 2
    left_max = find_max(arr[:mid])
    right_max = find_max(arr[mid:])
    return ___