算法基础——分治法和棋盘覆盖问题
编辑时间:2023/9/28
1.分治法
设计思想
将一个规模为n的大问题分割成k个子问题,原问题和子问题有相互独立性,其中n>=k>1。
解题步骤
一般来说,分治算法在每一层递归上的都有3个步骤。
- 分解:将原问题分解成一系列子问题
- 求解:递归求解各个子问题的解,若子问题比较小,直接求解。
- 合并:将子问题的解合并为原问题的解。
2.棋盘覆盖问题
棋盘覆盖
在一个2k×2k (k≥0)个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为特殊方格。显然,特殊方格在棋盘中可能出现的位置有4k种,因而有4k种不同的棋盘,图4.10(a)所示是k=2时16种棋盘中的一个。棋盘覆盖问题(chess cover problem)要求用图4.10(b)所示的4种不同形状的L型骨牌覆盖给定棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。【百度百科】
数学证明
设T(k)是算法ChessBoard覆盖一个2k×2k棋盘所需时间,从算法的划分
策略可知,T(k)满足如下递推式:
T(k) = 1 当k=0时
T(k) = 4T(k-1) 当k>0时
解此递推式可得T(k)=O(4^k)。【百度百科】
解决方法
利用分治法将原问题分解为规模较小的棋盘覆盖问题。 k>0时,可将2k×2k的棋盘划分为4个2(k-1)×2(k-1)的子棋盘。这样划分后,由于原棋盘只有一个特殊方格,所以,这4个子棋盘中只有一个子棋盘包含该特殊方格,其余3个子棋盘中没有特殊方格。为了将这3个没有特殊方格的子棋盘转化为特殊棋盘,以便采用递归方法求解,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种划分策略,直至将棋盘分割为1×1的子棋盘。【百度百科】
程序实现具体要求:
-
问题描述
求解内容:摆放骨牌的核心算法必须用递归+分治来实现棋盘覆盖。
输入格式:棋盘格子随机生成,无需键盘输入;特殊格子手动输入。
输出格式:输出一个最少8x8的棋盘,21个骨牌用数字1,2,…来表示每块骨牌 -
数学证明
数学证明说明棋盘刚好能被骨牌完全覆盖。
-
解题思路
(1)定义下列变量
t表示L型骨牌的编号,二维数组board 表示棋盘,tr tc 左上角的行列号,dr dc 特殊方格行列号,size 棋盘规模2k*2k
(2)步骤
大致思想为利用分治法将棋盘分为四个区域,其中第一块L骨型牌放在原棋盘的正中间,使得每个区域都有解,通过递归求解子棋盘再合并子棋盘的解得出原棋盘的解。
1.前提:
判断棋盘是否可分割,若原盘大小为1那么不分割。
2.否则L型骨牌号自增加1且分割棋盘,进行以下步骤。
第一步覆盖处理左上角棋盘:
特殊方格在棋盘内,递归划分棋盘。
特殊方格不在棋盘内,用t号L骨型牌覆盖右下角。
第二步覆盖处理棋盘右上角:
特殊方格在棋盘内,递归划分棋盘。
特殊方格不在棋盘内,用t号L骨型牌覆盖左下角。
第三步覆盖处理左下角棋盘:
特殊方格在棋盘内,递归划分棋盘。
特殊方格不在棋盘内,用t号L骨型牌覆盖右上角。
第四步覆盖处理右下角棋盘:
特殊方格在棋盘内,递归划分棋盘。
特殊方格不在棋盘内,用t号L骨型牌覆盖左上角。
- 算法描述
package test;
import java.util.Scanner;
import java.util.Random;
public class QiPan {
int tile=1;//表示L型骨牌的编号
int [][]board =new int[100][100];//表示棋盘
//tr tc 左上角的行列号
// dr dc 特殊方格行列号
// size 棋盘规模2^k*2^k
public void chessBoard(int tr,int tc ,int dr,int dc,int size){
//原盘大小为1 不分割
if(size==1){
return;
}
int t=tile++;//L型骨牌号
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);
}
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
Random r=new Random();
// 输出一个最少8x8的棋盘
int size=(int)Math.floor(8+Math.random()*13);
System.out.println("输入特殊格子位置");
int dr=sc.nextInt();
int dc=sc.nextInt();
//对象调用方法
QiPan q=new QiPan();
q.chessBoard(0,0,dr,dc,size);
for(int i=0;i<size;i++){
for(int j=0;j<size;j++){
System.out.print(String.format("%5d",q.board[i][j]));
}
System.out.println();
}
}
}
-
程序运行结果截图
-
时间及空间复杂度分析