分治者,分而治之也。
分治法(divide and conquer method)的设计思想是将一个难以直接解决的大问题,划分成一些规模较小的子问题,以便各个击破,分而治之。
由分治法产生的子问题往往是原问题的较小模式,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到能够直接求解,这自然导致递归。
分治与递归经常同时应用在算法设计之中,并由此产生许多高效的算法。
一般来说,分治法的求解过程由以下三个阶段组成:
(1)划分:把规模为n的原问题划分为k个规模较小的子问题。
(2)求解:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
(3)合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。
人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。也就是将一个问题划分成大小相等的k个子问题(通常k=2),这种使子问题规模大致相等的做法是出自一种平衡(bal-ancing)子问题的启发式思想。
算法设计实例——数字旋转方阵
【问题】 输出如图所示N×N(1≤N≤10)的数字旋转方阵。
【想法】 用二维数组data[N][N]表示N×N的方阵,观察方阵中数字的规律,可以从外层向里层填数。
在填数过程中,每一层的起始位置很重要。
设变量size表示方阵的大小,则初始时size=N,填完一层则size=size-2;设变量begin表示每一层的起始位置,变量i和j分别表示行号和列号,则每一层初始时i=begin,j=begin。将每一层的填数过程分为A、B、C、D四个区域,每个区域需要填写size-1个数字,且填写区域A时列号不变行号加1,填写区域B时行号不变列号加1,填写区域C时列号不变行号减1,填写区域D时行号不变列号减1。显然,递归的边界条件是size等于0或size等于1。
【算法】 设递归函数Full实现填数过程,算法用伪代码描述如下 :
算法:Full(number,begin,size)
输入:当前层左上角要填的数字number,左上角的坐标begin,方阵的阶数size
输出:数字旋转方阵
1.如果size等于0,则算法结束;
2.如果size等于1,则data[begin][begin]=number,算法结束;
3.初始化行、列下标i=begin,j=begin;
4.重复下述操作size-1次,填写区域A:
4.1 data[i][j]=number;number++;
4.2 行下标i++;列下标不变;
5.重复下述操作size-1次,填写区域B:
5.1 data[i][j]=number;number++;
5.2 行下标不变;列下标j++;
6.重复下述操作size-1次,填写区域C:
6.1 data[i][j]=number;number++;
6.2 行下标i--;列下标不变;
7.重复下述操作size-1次,填写区域D:
7.1 data[i][j]=number;number++;
7.2 行下标不变,列下标j--;
8.调用函数Full在size-2阶方阵中左上角begin+1处从数字number开始填数;
实现如下:
#include <stdio.h>
#define N 10
int data[N][N] = {0};
void Full(int number, int begin , int size);
void OutPrint(int size);
int main()
{
int n;
printf("请输入方阵的阶数(小于10): ");
scanf("%d", &n);
Full(1, 0, n);
OutPrint(n);
return 0;
}
void Full(int number, int begin, int size)
{
int i, j ,k;
if(size == 0)
return;
if(size == 1) //递归的边界
{
data[begin][begin] = number;
return;
}
i = begin, j = begin;
// 填写区域 A
for( k = 0; k<size-1; k++ ){
data[i][j] = number;
number ++;
i ++;
}
// 填写区域 B
for(k =0; k<size -1; k++){
data[i][j] = number;
number ++;
j++;
}
// 填写区域 C
for(k = 0; k<size-1; k++){
data[i][j]= number;
number ++;
i--;
}
// 填写区域 D
for(k = 0; k<size-1; k++){
data[i][j]= number;
number ++;
j--;
}
Full(number, begin+1, size-2); // 递归调用
}
void OutPrint(int size)
{
int i , j;
for( i = 0; i<size; i++ ){
for( j =0; j<size; j++)
printf("%4d", data[i][j]);
printf("\n");
}
return;
}
实测结果如下: