分治法
1.基本概念
在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”
,就是把一个复杂的问题分成两个或更多的相同或相似的**子问题
,再把子问题分成更小的子问题**……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……
任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。
例如,对于 n 个元素的排序问题,当 n=1 时,不需任何计算。n=2 时,只要作一次比较即可排好序。n=3 时只要作 3 次比较即可,…。而当 n 较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。
2.基本思想以及策略
- 分治法的设计思想: 将一个难以解决的
大问题
—>小问题
,逐一击破 - **分治策略是:**对于一个问题规模为 n的问题,分解到 **
k
**个小问题;最后将各个 子问题的解合并得到原问题的解注:分割到 最后 子问题 可以得到答案的程度,eg:求解斐波那契数列最小子问题就是前两项 分别为1,1 … - 我们通常使用递归的方法来当做分治法的手段
3.分治法的特征
分治法所能解决的问题一般具有以下几个特征:
- 该问题的规模缩小到一定的程度就可以容易地解决
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
- 利用该问题分解出的子问题的解可以合并为该问题的解;
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。
4.递归框架
function (parameters){ //parameter 参数
//递归出口
if(parameters == [某个值]){
return;
}
//分割问题
if([某情况..]){
function( new parameters);
}层
...........
}
例题:棋盘覆盖问题
棋盘覆盖问题的描述:
棋盘覆盖问题的分治求解思路:
1.解题思路
不难看出我们可以把一个大的棋盘分割成小的,当分割到1*1时就是一种临界值,即子棋盘大小size == 1 时 就是递归出口!
我们在画一个8*8的棋盘看看是否是这个规律
不难看出分割后规律也是如此,
- **总结:**不断分割棋盘,把分割后的棋盘进行判断,如果
子棋盘
无特殊方块,那我们就设置一个,如上图,所示
代码实现过程
主要框架
// 分割函数
int title = 0;
void DivideQiPan(QiPan* Q, int tr, int tc, int dr, int dc, int size){
// 结束条件 递归出口
if (size == 1) {
return;
}
// 分割(形式上分割)
int t = ++title;
int s = size / 2;
// 左上方
if (dr < tr + s && dc < tc + s) {
DivideQiPan(Q, tr, tc, dr, dc, s);
} else {
Q->Qi[tr + s - 1][tc + s - 1] = t;
DivideQiPan(Q, tr, tc, tr + s - 1, tc + s - 1, s);
}
// 右上方
if (dr < tr + s && dc >= tc + s) {
DivideQiPan(Q, tr, tc + s, dr, dc, s);
} else {
Q->Qi[tr + s - 1][tc + s] = t;
DivideQiPan(Q, tr, tc + s, tr + s - 1, tc + s, s);
}
// 左下方
if (dr >= tr + s && dc < tc + s) {
DivideQiPan(Q, tr + s, tc, dr, dc, s);
} else {
Q->Qi[tr + s][tc + s - 1] = t;
DivideQiPan(Q, tr + s, tc, tr + s, tc + s - 1, s);
}
// 右下方
if (dr >= tr + s && dc >= tc + s) {
DivideQiPan(Q, tr + s, tc + s, dr, dc, s);
} else {
Q->Qi[tr + s][tc + s] = t;
DivideQiPan(Q, tr + s, tc + s, tr + s, tc + s, s);
}
}
完整代码
#include <stdio.h>
#define MAXSIZE 16
int title = 0;
//棋盘结构体
//------------------------------
typedef struct {
int Qi[MAXSIZE][MAXSIZE];
int size;
} QiPan;
//------------------------------
//-------------------算法函数----------------------
/*
* 初始化棋盘*/
QiPan initQiPan(QiPan Q);
/*
* 输入特殊方块*/
void input(int *tr,int*tc);
/*
* 分割统计棋盘*/
void DivideQiPan(QiPan* Q,int tr,int tc,int dr,int dc,int size);
/*
*遍历棋盘
*/
void TraverseQiPan(QiPan Q){
for (int i = 0; i < Q.size; ++i) {
for (int j = 0; j < Q.size; ++j) {
printf("%d\t",Q.Qi[i][j]);
}
printf("\n");
}
}
//------------------------------------------------
int main() {
// 特殊方块行和列
int dr; // 行
int dc; // 列
// 分割后的 棋盘左上方第一个开始位置 用于判断特殊方块位置
int tr = 0;
int tc = 0;
QiPan Q = {};
// 初始化棋盘
Q = initQiPan(Q);
printf("分割前的棋盘\n");
printf("-------------------------------------------------------------\n");
TraverseQiPan(Q);
printf("-------------------------------------------------------------\n");
// 输入特殊方块
input(&dr,&dc);
// 开始分治法填充棋盘
DivideQiPan(&Q,tr,tc,dr-1,dc-1,Q.size);
printf("分割后的棋盘\n");
printf("-------------------------------------------------------------\n");
TraverseQiPan(Q);
printf("-------------------------------------------------------------\n");
printf("一共填充了 %d 个目标方块",title);
}
//--------------------------------------------------------------
//-----------------------函数实现---------------------------------
/* 判断一个数是否为 2 的 次方
* if(n <= 0) {
return 0;
}
return (n & (n - 1)) == 0;
* */
QiPan initQiPan(QiPan Q) {
int mark = 1;
do {
printf("请输入棋盘的长度(2^n 且 棋盘长度小于16)长度\n");
scanf("%d", &Q.size);
int temp = Q.size;
// 判断Q.size输入是否合法
if ((temp & (temp - 1)) == 0) {
printf("输入合法!\n");
mark = 0;
} else {
printf("输入不合法!");
}
} while (mark);
// 给矩阵初始化为 0
for (int i = 0; i < Q.size; ++i) {
for (int j = 0; j < Q.size; ++j) {
Q.Qi[i][j] = 0;
}
}
return Q;
}
void input(int *dr,int*dc){
printf("请输入特殊方块位置:\n");
printf("请输入行: ");
scanf("%d",dr);
printf("请输入列: ");
scanf("%d",dc);
printf("\n");
}
void DivideQiPan(QiPan* Q, int tr, int tc, int dr, int dc, int size) {
// 结束条件 递归出口
if (size == 1) {
return;
}
// 分割(形式上分割)
int t = ++title;
int s = size / 2;
// 左上方
if (dr < tr + s && dc < tc + s) {
DivideQiPan(Q, tr, tc, dr, dc, s);
} else {
Q->Qi[tr + s - 1][tc + s - 1] = t;
DivideQiPan(Q, tr, tc, tr + s - 1, tc + s - 1, s);
}
// 右上方
if (dr < tr + s && dc >= tc + s) {
DivideQiPan(Q, tr, tc + s, dr, dc, s);
} else {
Q->Qi[tr + s - 1][tc + s] = t;
DivideQiPan(Q, tr, tc + s, tr + s - 1, tc + s, s);
}
// 左下方
if (dr >= tr + s && dc < tc + s) {
DivideQiPan(Q, tr + s, tc, dr, dc, s);
} else {
Q->Qi[tr + s][tc + s - 1] = t;
DivideQiPan(Q, tr + s, tc, tr + s, tc + s - 1, s);
}
// 右下方
if (dr >= tr + s && dc >= tc + s) {
DivideQiPan(Q, tr + s, tc + s, dr, dc, s);
} else {
Q->Qi[tr + s][tc + s] = t;
DivideQiPan(Q, tr + s, tc + s, tr + s, tc + s, s);
}
}
结果