(四)分而治之策略——算法设计与分析


一、分而治之

1.1 思想

我们在求解一些复杂问题的时候,因为问题规模较大,难以理清思路。自然便会想到,把一个复杂、大规模的问题拆解成若干个简单、小规模的问题

拆解得到的小问题与大问题的本质相同,但是对于小问题,我们更容易看清它的本质,更容易求解。这便是分而治之策略的思想。


1.2 框架

基于分而治之思想设计的算法一般有以下3个步骤组成:

分而治之一般步骤:

  1. 分解原问题:将原问题分解为若干个不相交的子问题
  2. 解决子问题:递归地求解各个子问题
  3. 合并问题解:将子问题的解合并为原问题的解

因此,其时间复杂度分为3个部分:划分时间;求解时间;合并时间。

1.3 适用情况

在我们自己设计算法的时候,可以根据问题的特征来设计分治算法。

分治算法能解决的问题的特征:

  1. 问题规模缩小到一定程度可以很容易解决
  2. 该问题具有最优子结构性质(即问题可以分解为若干个规模较小的相同问题)
  3. 子问题之间不交叉(即分解出的子问题之间是相互独立的)
  4. 分解出的子问题的解可以合并为该问题的解

二、应用

分而治之的精髓在于,针对不同的问题,巧妙地设计分与合的策略,可以取得令人满意的效果。

其中很典型的两个例子便是归并排序与快速排序,一个侧重分,一个侧重合。通过这两种算法的学习,可以更深刻地体会到分而治之思想的妙处。

推荐大家观看北航童咏昕教授的课程,简洁明了,清晰易懂。

2.1 归并排序

视频链接说明
归并排序分而治之经典算法,侧重于
最大子数组问题基于归并排序的应用
逆序对计数问题基于归并排序的应用

归并排序,归指递归,并指合并。

基于分而治之思想设计的算法都要使用到递归,因此必须掌握对递归式的时间复杂度分析,可以参考我之前的一篇博客,介绍了4种分析递归式时间复杂度的方法,简洁易懂易用。

依据童教授的讲解,这里给出我自己实现的归并排序代码(C语言),水平有限,仅供参考学习。

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>

void MergeSort(int *arr, int low, int high);
void Merge(int *arr, int low, int mid, int high);

int main()
{
    int i;
    int arr[] = {70, 25, 23, 55, 31, 20, 22, 13, 62, 81, 50, 74, 65, 84, 80, 66, 64, 42, 12, 40};
    MergeSort(arr, 0, 19);
    for (i = 0; i < 20; i++)
        printf("%d ", arr[i]);
    system("pause");
    return 0;
}

void MergeSort(int *arr, int low, int high)
{
    if (low >= high)
        return;
    int mid = (low + high) / 2;
    MergeSort(arr, low, mid);
    MergeSort(arr, mid + 1, high);
    Merge(arr, low, mid, high);
}

void Merge(int *arr, int low, int mid, int high)
{
    int i, j, k;
    int *assist = (int *)malloc(sizeof(int) * (high + 1)); // 辅助数组,将两部分copy过来
    for (k = low; k <= high; k++)
        assist[k] = arr[k];
    k = low;
    i = low;
    j = mid + 1;
    while (i <= mid && j <= high)
    {
        if (assist[i] <= assist[j])
            arr[k] = assist[i++];
        else
            arr[k] = assist[j++];
        k++;
    }
    while (i <= mid)
        arr[k++] = assist[i++];
    while (j <= high)
        arr[k++] = assist[j++];
}

2.2 快速排序

视频链接说明
快速排序分而治之经典算法,侧重于
次序选择问题基于快速排序的应用

快速排序有多种实现方式,但重要的是理解其中的思想,关于其具体实现方式,大家可以自行查阅别的资料。

依据童教授的讲解,这里给出我自己实现的快速排序代码(C语言),水平有限,仅供参考学习。

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

void QuickSort(int *arr, int low, int high);
int QuickSort_Partition(int *arr, int low, int high);
void QuickSort_RandomPivot(int *arr, int low, int high);

int main()
{
    int i;
    int arr[] = {70, 25, 23, 55, 31, 20, 22, 13, 62, 81, 50, 74, 65, 84, 80, 66, 64, 42, 12, 40};
    QuickSort(arr, 0, 19);
    for (i = 0; i < 20; i++)
        printf("%d ", arr[i]);
    system("pause");
    return 0;
}

void QuickSort(int *arr, int low, int high)
{
    if (low < high)
    {
        int pivot = QuickSort_Partition(arr, low, high);
        QuickSort(arr, low, pivot - 1);
        QuickSort(arr, pivot + 1, high);
    }
}

int QuickSort_Partition(int *arr, int low, int high)
{
    int i, j; // i作为分水岭,比主元小的在i左侧,比主元大的在右侧,i+1为第一个比主元大的
    int temp;
    QuickSort_RandomPivot(arr, low, high); // 随机选取主元
    int pivot = high;                      // 选取主元
    i = low - 1;                 // 初始化i为low-1,j为low
    for (j = low; j < high; j++) // 遍历所有元素
    {
        if (arr[j] <= arr[pivot]) // 当前元素比主元小,和i+1交换下
        {
            temp = arr[j];
            arr[j] = arr[i + 1];
            arr[i + 1] = temp;
            i++; // i右移
        }
    }
    temp = arr[pivot]; // 把主元放在中间做分界线
    arr[pivot] = arr[i + 1];
    arr[i + 1] = temp;
    return i + 1;
}

void QuickSort_RandomPivot(int *arr, int low, int high)
{
    srand((unsigned)time(NULL));
    int num = rand();
    int pivot = low + num % (high - low + 1);
    int temp;
    temp = arr[high];
    arr[high] = arr[pivot];
    arr[pivot] = temp;
}

2.3 其他应用

基于分而治之的算法有很多,这里列举几个,感兴趣的读者可以自行查阅。

  • 大整数乘法
  • 矩阵乘法
  • 快速傅里叶变换
  • 线性时间选择算法
  • 最邻近点对
  • 凸包算法
  • 数据剪除方法
  • 排序与线性时间排序
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

友人帐_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值