目录

一、分治法

1. 基本原理

(1)分解

(2)解决

(3)合并

2. 应用场景

3. 算法实例

快速排序

归并排序

4. 效率

5. 优缺点

优点:

缺点:

二、问题描述与分析

1.问题

2.分析与算法设计

(1)算法设计与分析

<1>. 问题分解

<2>. 解决子问题

<3>. 合并解决方案

(2)算法效率

2.结论

三、示例

分治法的步骤和示例

1.分解问题

2.解决子问题

3.合并结果

递归合并过程详解

第一轮分解与解决

第二轮合并

最终合并

结果

四、代码实现

代码分析

1. 比较函数

2. 查找最小和最大值的函数

3. 主函数 main

代码运行结果


一、分治法

分治法(Divide and Conquer)是一种算法设计范式,广泛应用于计算机科学中,用于解决各种复杂的问题。它的核心是将一个大问题分解成几个小问题,逐个解决这些小问题,然后将小问题的解合并来解决原始的大问题。以下是分治法的详细介绍:

1. 基本原理

分治策略包括三个基本步骤:

(1)分解

将原问题分解成一系列子问题,这些子问题相互独立且与原问题性质相同。

(2)解决

递归地解决这些子问题。如果子问题的规模足够小,那么就直接解决它们。

(3)合并

将所有子问题的解合并成原问题的解。

2. 应用场景

分治法适用于问题能够被分解为多个规模较小的相同问题的场景。常见的应用包括:

  • 排序算法:如快速排序和归并排序。快速排序将数组分成较小的两部分,然后独立排序;归并排序将数组一分为二,递归排序后合并。
  • 数学问题:如计算大数的乘积或Karatsuba乘法。
  • 图形学中的问题:如快速傅里叶变换(FFT)。
  • 最近点对问题:在平面上找到最近的一对点。

3. 算法实例

快速排序

快速排序是分治法的一个经典应用。其过程可以描述如下:

  1. 分解:选择一个元素作为基准,重新排列数组,使得左侧元素小于等于基准,右侧元素大于等于基准。
  2. 解决:递归地对左右两侧的数组进行快速排序。
  3. 合并:由于递归调用已经在内部完成了数组的排序,不需要额外的合并操作。

归并排序

归并排序也是一个典型的分治法应用:

  1. 分解:将数组分成两半。
  2. 解决:递归地对这两半进行归并排序。
  3. 合并:将两个有序的子数组合并成一个有序数组。

4. 效率

分治法的效率取决于子问题划分的均匀性以及合并步骤的复杂度。如果每次分解能将问题均匀地分成几乎等大的子问题,且合并的复杂度不高,则分治法通常能提供良好的性能,尤其是对于递归处理自然适合的问题。

5. 优缺点

优点

  • 分治法可以简化复杂问题的解决过程
  • 通常能提高算法的效率,并易于通过递归实现

缺点

  • 分治法在某些问题上可能会导致过多的递归调用,增加了堆栈的使用。
  • 如果问题不适合均匀分解,或者合并步骤复杂度过高,可能不会带来性能上的优势

分治法的成功与否很大程度上依赖于问题是否适合这种方法。在适用的情况下,它是一个非常强大的工具,能够有效地解决各种复杂问题。

二、问题描述与分析

1.问题

【老板有一袋金块(共n块),两名员工每人可以得到其中的一块,排名第一的员工可以得到最重的金块,排名最后的员工的则得到袋子中最轻的金块。】

2.分析与算法设计

(1)算法设计与分析

在一袋中共有 n 块金块的情况下,找出最重的和最轻的金块分别给排名第一和最后的员工——我们可以采用分治策略。这种策略能够有效地同时确定一组数中的最大值和最小值。以下是这种策略的详细分析:

<1>. 问题分解

在分治法中,首先将问题分解为更小的子问题。对于本问题,可以将金块的列表一分为二,分成两个较小的子列表。这种分解持续进行,直到每个子列表包含一块或两块金块。

<2>. 解决子问题

对于包含单一金块的子列表,这块金块自然是该列表中的最重和最轻的。对于包含两块金块的子列表,通过一次比较可以确定哪块更重,哪块更轻。

<3>. 合并解决方案

合并过程是分治法中的关键步骤。对于每对已解决的子问题,我们需要将它们的解合并,以得到更大范围内的最大值和最小值。这通过比较各个子列表的最大值和最小值来实现:

  • 将两个子列表的最小值进行比较,更新当前合并后列表的最小值。
  • 将两个子列表的最大值进行比较,更新当前合并后列表的最大值。

(2)算法效率

使用这种分治策略,每层递归处理都会涉及到对半分的金块进行比较。在最坏的情况下,比较次数将是 3×(n/2)​,因为每一对需要三次比较(两次用于比较子问题内的金块,一次用于合并时比较最大或最小)。这使得算法的总比较次数大约是 1.5nlog⁡n,但实际上每次递归分割后需要的比较次数更少,因为顶层比较较多,下层比较较少。

2.结论

尽管直接扫描法(一次遍历确定最大最小值,时间复杂度为 O(n))在此问题上可能更直接和快速,分治法提供了一个重要的算法设计视角,尤其在处理需要分布式或并行计算的更复杂问题时表现出其优势。对于实际应用,选择哪种算法应根据具体问题的规模和可用计算资源来定。

三、示例

假设老板有一袋共有8块金块,这些金块的重量分别为(单位:克):

    500,300,600,200,450,370,880,210500,300,600,200,450,370,880,210

分治法的步骤和示例

1.分解问题

将金块列表不断二分,直到每个子问题只包含一个金块或两个金块。

2.解决子问题

对每个子问题(一块或两块金块),确定最轻和最重的金块。

3.合并结果

对每对子问题的结果进行合并,找出这两个子问题中最轻和最重的金块。

递归合并过程详解

初始列表:500,300,600,200,450,370,880,210,500,300,600,200,450,370,880,210

  • 第一轮分解与解决

    • 子列表1:500,300500,300 ——> 最轻:300, 最重:500
    • 子列表2:600,200600,200 ——> 最轻:200, 最重:600
    • 子列表3:450,370450,370 ——> 最轻:370, 最重:450
    • 子列表4:880,210880,210 ——> 最轻:210, 最重:880
  • 第二轮合并

    • 合并1:比较 300,500300,500 与 200,600200,600
      • 最轻:200, 最重:600
    • 合并2:比较 370,450370,450 与 210,880210,880
      • 最轻:210, 最重:880
  • 最终合并

    • 比较 200,600200,600 与 210,880210,880
      • 最轻:200, 最重:880

结果

在这个例子中,使用分治法成功找到了最轻的金块(200克)和最重的金块(880克)。排名第一的员工将得到880克的金块,而排名最后的员工将得到200克的金块。

这个过程展示了分治法如何通过逐步分解问题、独立解决更小的问题,然后将结果有效合并来解决复杂问题。在实际应用中,这种方法特别适用于可以并行处理的情境,有效利用计算资源,尤其是在数据量较大时。

四、代码实现

#include<stdio.h> 

//比较两个整数并返回较小的一个
int min(int x, int y)
{
    if (x < y)
        return x;
    else
        return y;
}

//比较两个整数并返回较大的一个
int max(int x, int y)
{
    if (x > y)
        return x;
    else
        return y;
}

//递归函数,用于找到数组中的最小值
int Find_min(int A[], int left, int right)
{
    int la, ma, ra;
    if (left == right)    //只有一个元素时,返回该元素
    {
        int min;
        min = A[right];
        return min;
    }
    if (right - left == 1)    //只有两个元素时,返回两者中较小的一个
    {
        la = A[left];
        ra = A[right];
        return(min(la, ra));
    }
    if (right - left > 1)   //多于两个元素时,递归查找
    {
        ma = (left + right) / 2;
        la = Find_min(A, left, ma); //递归找左半部分的最小值
        ra = Find_min(A, ma + 1, right); //递归找右半部分的最小值
        return(min(la, ra)); //返回两部分中的较小值
    }
}

//递归函数,用于找到数组中的最大值
int Find_max(int A[], int left, int right)
{
    int la, ma, ra;
    if (left == right) //只有一个元素时,返回该元素
    {
        int max;
        max = A[right];
        return max;
    }
    if (right - left == 1) //只有两个元素时,返回两者中较大的一个
    {
        la = A[left];
        ra = A[right];
        return(max(la, ra));
    }
    if (right - left > 1) //多于两个元素时,递归查找
    {
        ma = (left + right) / 2;
        la = Find_max(A, left, ma); //递归找左半部分的最大值
        ra = Find_max(A, ma + 1, right); //递归找右半部分的最大值
        return(max(la, ra)); //返回两部分中的较大值
    }
}

int main()
{
    int A[100]; //存储金块重量的数组
    int n; //金块的数量
    int min; //用于存储最轻金块的变量
    int max; //用于存储最重金块的变量
    printf("请输入金块数目:");
    scanf_s("%d", &n);
    printf("请输入各金块的重量:");
    for (int i = 0; i < n; i++)
        scanf_s("%d", &A[i]); //输入金块的重量
    printf("最重的金块:");
    max = Find_max(A, 0, n - 1); //调用函数找最重的金块
    printf("%d", max);
    printf("\n");
    printf("最轻的金块:");
    min = Find_min(A, 0, n - 1); //调用函数找最轻的金块
    printf("%d", min);
    printf("\n");
    return 0;
}

代码分析

上述C程序使用了分治法来找出一组金块中的最重和最轻的金块。程序主要由两个核心函数组成——Find_minFind_max——以及主函数 main,它负责与用户交互和调用这两个函数。下面详细解释每个部分的功能和逻辑:

1. 比较函数

  • int min(int x, int y)
    • 这个函数比较两个整数 xy,返回它们中的较小值。
    • 使用条件表达式 if (x < y) 来判断并返回较小的值。
  • int max(int x, int y)
    • 这个函数比较两个整数 xy,返回它们中的较大值。
    • 使用条件表达式 if (x > y) 来判断并返回较大的值。

2. 查找最小和最大值的函数

  • int Find_min(int A[], int left, int right)
    • 这个函数使用递归分治法来找出数组 A[](从索引 leftright)中的最小值。
    • 基本情况是当区间只包含一个元素 (left == right),此时直接返回该元素。
    • 如果区间包含两个元素 (right - left == 1),使用 min 函数比较这两个元素,返回较小的一个。
    • 对于更大的区间,函数将数组分为两部分,分别递归找出每部分的最小值,然后使用 min 函数比较这两个最小值,返回最小的一个。
  • int Find_max(int A[], int left, int right)
    • 类似于 Find_min,这个函数使用递归分治法来找出数组 A[](从索引 leftright)中的最大值。
    • 基本逻辑与 Find_min 相同,但使用的是 max 函数来比较并返回两个值中的较大值。

3. 主函数 main

  • 主函数首先定义一个能存储100个整数的数组 A[],用来存储金块的重量。
  • 接着,它从用户处获取金块的总数 n 和每块金块的重量。
  • 使用 Find_max 函数来计算这些金块中最重的一块,并将结果输出。
  • 使用 Find_min 函数来计算这些金块中最轻的一块,并将结果输出。

代码运行结果

希望对大家有帮助!!!

参考资料

《算法设计与分析》(上海交通大学出版社)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐