分治思想,分治策略,自古有之,与人类生活息息相关,其本质是将大问题拆解为小问题,小问题转换为已知解的问题,进而求解。
军队管理,国家分级治理……
大规模数据排序,例如10000000000万个数,规模大的时候,分治思想就很重要了。
基本思想
分!如果问题还大,就再分!直到其变成容易求解的基本问题,这个过程其实与递归的类似的。
总而言之,递归思想与分治策略,有着密不可分的联系。
对于分解之后的基本问题,我们就可以使用一些基本的策略求解,因为容易,比如枚举。
核心策略
- 分:大问题–> 小问题 --> 基本问题
- 治:解决基本问题
- 合:将基本问题的解合并,得到原问题的解
抽象的理论内容,很重要,我们把握其核心思想和本质,但是,想要理解抽象,我们更应该解决实际问题,在实际问题中理解抽象概念,达到融会贯通。
基本框架程序
- 理解问题
- 分解问题,找到自己认识的部分;或者,从最简单情况出发,递推地分析一下
- 获得简单问题的求解办法
- 将大问题递归地分解为简单问题去处理
- 将每个小问题的解合并
如此抽象,说了也白说,直接上例子。
归并排序
最难点:【合】的策略,合无定法!
分治策略与递归的对应关系
分:进行递归操作
治:递归终止条件和处理办法
合:递归处理办法
对于归并排序
- 分:将数组不断进行二分,直到其剩余1个元素
- 治:对于1个元素,一定是有序的,就可以将其作为基本问题的解返回
- 合:对于基本问题的解,需要让其合并后有序,因此合并需要专门的一些策略,针对归并排序特点进行设计。
合是最难的,因为合无定法,每种问题的每种解法,可能都不一样。
看代码:
#include <ctime>
#include <iostream>
using namespace std;// 给你一个规模很小的数组,并且告诉你分两半,每一半都是有序的,让你给合起来依然有序
// 合
void merge(int data[], int buffer[], int min, int mid, int max) {int first_mid_pointer = min; // data[min, mid]int second_mid_pointer = mid + 1; // data[mid+1, max]int buffer_pointer = min; // buffer[min, max]while (first_mid_pointer <= mid && second_mid_pointer <= max) {if (data[first_mid_pointer] <= data[second_mid_pointer]) {buffer[buffer_pointer] = data[first_mid_pointer];buffer_pointer++;first_mid_pointer++;}else{buffer[buffer_pointer++] = data[second_mid_pointer++];}}// 传递剩余部分if (first_mid_pointer <= mid) {while (first_mid_pointer <= mid && buffer_pointer <= max) {buffer[buffer_pointer++] = data[first_mid_pointer++];}} else {while (second_mid_pointer <= max && buffer_pointer <= max) {buffer[buffer_pointer++] = data[second_mid_pointer++];}}}// 归并排序
void merge_sort(int data[], int buffer[], int min, int max) {// 治if (min >= max)return;if (min < max){// 分int mid = (max + min) / 2; // + ;not -merge_sort(data, buffer, min, mid);merge_sort(data, buffer, mid + 1, max);// 合(“治”之后才会执行)merge(data, buffer, min, mid, max);// buffer result --> datafor (int i = min; i <= max; i++) { // 注意起点和终点,注意“=”data[i] = buffer[i];}}
}int main()
{clock_t start, end;for (int n = 64; ; n *= 2) {// initializationint *a = new int[n];for (int i = 0; i < n; i++) {a[i] = rand() % 10000 + 1;}// merge sortstart = clock();int length = n; //sizeof(a) / sizeof(a[0]);int *buffer = new int[length];merge_sort(a, buffer, 0, length - 1);end = clock();cout << "N = " << n << " time = " << end - start << endl;if ((end - start)*2 > 180000) // predict: more than 3 minutes,stop!break;}return 0;}
类似版本的代码
#include <iostream>
using namespace std;// combine function
// data sequence: min to max
void combine(int data[], int buffer[], int min, int mid, int max) {// NOTE: max = length of array - 1int before_mid_pointer = min; // data[min,mid]int after_mid_pointer = mid + 1; // data[mid+1,max]int buffer_pointer = min; // buffer[min,max]while (before_mid_pointer <= mid && after_mid_pointer <= max){if (data[before_mid_pointer] <= data[after_mid_pointer])buffer[buffer_pointer++] = data[before_mid_pointer++];elsebuffer[buffer_pointer++] = data[after_mid_pointer++];}// process the rest of data that is not be compared// add them to buffer directlywhile (before_mid_pointer <= mid && buffer_pointer <= max){buffer[buffer_pointer++] = data[before_mid_pointer++];}while (after_mid_pointer <= max && buffer_pointer <= max){buffer[buffer_pointer++] = data[after_mid_pointer++];}// add buffer to datafor (int i = min; i <= max; i++) {data[i] = buffer[i];}}// 自始至终,我们都是使用一个data和一个buffer,通过指针值“虚拟地”分割和排序。
// merge sort
void merge_sort(int data[], int buffer[], int min, int max) {if (max > min) {// divideint mid = (max + min) / 2;merge_sort(data, buffer, min, mid);merge_sort(data, buffer, mid + 1, max);// combine: sort two sorted arrays using specified methodcombine(data, buffer, min, mid, max);} else {// conquer: only 1 elementreturn;}
}int main() {int data[8] = { 1,3,5,0,100,4,33,7 };int buffer[8] = { 0 };merge_sort(data, buffer, 0, 7);for (int i = 0; i < 8; i++) {cout << buffer[i] << " ";}return 0;
}
小结
分治思想,用一个成语就是庖丁解牛,完整的牛我们搞不懂,就将其合理地拆解,分成小部分,然后就可以搞定了。
还需要大量实例去体会分、治、合的思想策略。
我们知道了分治策略与递归思想的结合。
治理的是基本问题,也就是递归的结束条件和结束时候的处理办法。
而分则是递归调用过程,它指明了我们应该如何调用递归函数去分割问题,使其更简化。
最后,合,则是在某个递归函数返回之后的处理办法,它也在递归处理过程内,只有递归返回后,才会执行,用于合并返回的基本问题的解,从而得到最终的复杂问题的解。
对于归并排序,我们有一个比较神奇的点,那就是,自始至终,data和buffer都使用的是一个,而所谓的分割,都是通过虚拟的指针来实现的,我们并没有真地将其分割!
算法学习策略
- 原理与思想本质
- 实例与图解
- 实际问题分析
- 抽象
- 实现
需要这几个过程,才能真正体会算法思想的精髓,通过大量实例和图解,能够更好地理解算法,理解思想本质。