前言
一、分治的概念
分治法的基本思想:
将一个难以直接解决的大问题,分割成一些规模较小的相同问题,更好地解决,然后将求出的小规模问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。
分治法解题的一般步骤:
(1)分解:将要解决的问题划分成若干规模较小的同类问题
(2)求解:当子问题划分得足够小时,用较简单的方法解决
(3)合并:按原问题的要求,将子问题的解逐层合并构成原问题的解。
注意:
分治法的适用条件:该问题所分解出的各个子问题是相互独立的,子问题之间不包含公共的子问题。
二、经典例题
1、棋盘覆盖问题
问题描述:
在一个2k×2k (k≥0)个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为特殊方格。显然,特殊方格在棋盘中可能出现的位置有4k种,因而有4k种不同的棋盘,图(a)所示是k=2时16种棋盘中的一个。棋盘覆盖问题(chess cover problem)要求用图(b)所示的4种不同形状的L型骨牌覆盖给定棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。
问题分析:
①首先要将问题规模缩小,当棋盘大小为 2 X 2可以找到问题的解。如图:
此时,当特殊方格在左上角、右上角、左下角或是右下角时,一块L型骨牌都能恰好覆盖棋盘。
②棋盘可以划分成四个部分:左上角、左下角、右上角
、右下角,特殊方格必定在其中一个部分。
③但是缩小问题规模时,子问题之间必须是相同或相似问题,因此棋盘划分为四个部分之后要为其他三个没有特殊方格的部分构建特殊方格,就可以在四部分交界处放置一个L型骨牌,使得剩余三个部分也成为残缺棋盘。如图:
算法思路:
利用分治法,将方形棋盘分成4部分,如果该特殊方格在某一部分,就对该部分进行递归,如果不在某一部分,就假设一个方格为特殊方格,同样递归下去,直到全部覆盖。
左上角的子棋盘(若不存在特殊方格):则将该子棋盘右下角的那个方格假设为特殊方格;
右上角的子棋盘(若不存在特殊方格):则将该子棋盘左下角的那个方格假设为特殊方格;
左下角的子棋盘(若不存在特殊方格):则将该子棋盘右上角的那个方格假设为特殊方格;
右下角的子棋盘(若不存在特殊方格):则将该子棋盘左上角的那个方格假设为特殊方格。
C语言代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define MAX 1025
//问题表示
int k; //棋盘大小
int x, y; //特殊方格的位置
//求解问题表示
int board[MAX][MAX];
int tile = 1;
void ChessBoard(int tr, int tc, int dr, int dc, int size)
{
if (size == 1) return; //递归出口
int t = tile++; //取一个L型骨,其牌号为tile
int s = size / 2; //分割棋盘
//考虑左上角象限
if (dr<tr + s && dc<tc + s) //特殊方格在此象限中
ChessBoard(tr, tc, dr, dc, s);
else //此象限中无特殊方格
{
board[tr + s - 1][tc + s - 1] = t; //用t号L型骨牌覆盖右下角
ChessBoard(tr, tc, tr + s - 1, tc + s - 1, s);
//将右下角作为特殊方格继续处理该象限
}
//考虑右上角象限
if (dr<tr + s && dc >= tc + s)
ChessBoard(tr, tc + s, dr, dc, s);//特殊方格在此象限中
else //此象限中无特殊方格
{
board[tr + s - 1][tc + s] = t; //用t号L型骨牌覆盖左下角
ChessBoard(tr, tc + s, tr + s - 1, tc + s, s);
//将左下角作为特殊方格继续处理该象限
}
//处理左下角象限
if (dr >= tr + s && dc<tc + s) //特殊方格在此象限中
ChessBoard(tr + s, tc, dr, dc, s);
else //此象限中无特殊方格
{
board[tr + s][tc + s - 1] = t; //用t号L型骨牌覆盖右上角
ChessBoard(tr + s, tc, tr + s, tc + s - 1, s);
//将右上角作为特殊方格继续处理该象限
}
//处理右下角象限
if (dr >= tr + s && dc >= tc + s) //特殊方格在此象限中
ChessBoard(tr + s, tc + s, dr, dc, s);
else //此象限中无特殊方格
{
board[tr + s][tc + s] = t; //用t号L型骨牌覆盖左上角
ChessBoard(tr + s, tc + s, tr + s, tc + s, s);
//将左上角作为特殊方格继续处理该象限
}
}
int main()
{
int a, b, s;
printf("请输入棋盘的行号");
scanf("%d", &s);
printf("请输入特殊方格的行列号");
scanf("%d%d", &a, &b);
ChessBoard(1, 1, a, b, s);
for (int i = 1; i <= s; i++) {
for (int j = 1; j <= s; j++) {
printf("%4d", board[i][j]);
}
printf("\n");
}
return 0;
}
运行结果截图: