一、分而治之
1.1 思想
我们在求解一些复杂问题的时候,因为问题规模较大,难以理清思路。自然便会想到,把一个复杂、大规模的问题拆解成若干个简单、小规模的问题。
拆解得到的小问题与大问题的本质相同,但是对于小问题,我们更容易看清它的本质,更容易求解。这便是分而治之策略的思想。
1.2 框架
基于分而治之思想设计的算法一般有以下3个步骤组成:
分而治之一般步骤:
- 分解原问题:将原问题分解为若干个不相交的子问题
- 解决子问题:递归地求解各个子问题
- 合并问题解:将子问题的解合并为原问题的解
因此,其时间复杂度分为3个部分:划分时间;求解时间;合并时间。
1.3 适用情况
在我们自己设计算法的时候,可以根据问题的特征来设计分治算法。
分治算法能解决的问题的特征:
- 问题规模缩小到一定程度可以很容易解决
- 该问题具有最优子结构性质(即问题可以分解为若干个规模较小的相同问题)
- 子问题之间不交叉(即分解出的子问题之间是相互独立的)
- 分解出的子问题的解可以合并为该问题的解
二、应用
分而治之的精髓在于分
与合
,针对不同的问题,巧妙地设计分与合的策略,可以取得令人满意的效果。
其中很典型的两个例子便是归并排序与快速排序,一个侧重分,一个侧重合。通过这两种算法的学习,可以更深刻地体会到分而治之思想的妙处。
推荐大家观看北航童咏昕教授的课程,简洁明了,清晰易懂。
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 其他应用
基于分而治之的算法有很多,这里列举几个,感兴趣的读者可以自行查阅。
- 大整数乘法
- 矩阵乘法
- 快速傅里叶变换
- 线性时间选择算法
- 最邻近点对
- 凸包算法
- 数据剪除方法
- 排序与线性时间排序