Algorithm Experiment:递归与分治策略之棋盘覆盖问题【Recursion And Divide And Conquer Strategy——Chess Cover Problem】

算法的基本思想和应用要点

递归与分治算法是一种很经典的问题求解策略,其基本思想是:将一个规模较大的大问题,划分为多个相同相同类型规模较小的小问题,进而一次或多次递归调用自身求解小问题的解,综合所有小问题的解得到大问题的解。就这样通过不断反复的分割和综合,总可以得到能直接得到小问题解的情况,也就是我们所说的递归出口。简单的说就是:将大问题转换成相同的小问题,并且存在一个递归出口。
其应用要点可以用“分治是思想,递归是手段,二者相辅相成”来概括。运用递归与分治策略要求在每一次进行分割递归时,应遵循三个步骤:
分解(Divide):将原问题分解成一系列规模较小的子问题;
解决(Conquer):递归的解决各子问题。若子问题足够小,则直接求解;
合并(Combine):将子问题的结果合并成原问题的解。
另外,在分解时,问题的规模越小越容易解决,且各个子问题间是相互独立的,不存在重叠共线的问题。

问题描述

棋盘覆盖问题(Chess Cover Problem):在一个2k*2k个方格组成的棋盘中,若恰有一个方格与其他方格不同,则称该方格为一特殊方格,且称该棋盘为一特殊棋盘。要求用下图所示的四种不同形态的L形骨牌覆盖给定的特殊棋盘上除特殊方格外的所有方格,且任何两个L行骨牌不得重复覆盖。
在这里插入图片描述

理论分析

棋盘无论是从行还是列来看都是对称的,并且行和列长度相同,准确的说行和列的长度都可以被2整除,因此2k2k的棋盘可以采用分治法进行降维可以划分成4个2(k-1)2(k-1)大小相同的小棋盘。因为原棋盘只有一个特殊方格,所以划分后的4个小棋盘只有一个小棋盘拥有该特殊方格,另外三个没有。
采用递归与分治策略解决棋盘覆盖问题时,可以用一个L型骨牌覆盖这3个小棋盘的交汇点。从而达到将大规模问题转化为四个规模相对较小的问题。然后再递归的使用这种划分策略,一直到将棋盘划分为20
20也就是1
1的小棋盘。
在这里插入图片描述
递归填充策略:
如果特殊方块在左上角的子棋盘,则递归填充左上角子棋盘;否则递归填充左上角自棋盘的右下角,将右下角看做特殊方块,然后递归填充左上角子棋盘。
如果特殊方块在右上角的子棋盘,则递归填充右上角子棋盘;否则递归填充右上角子棋盘的左下角,将左下角看做特殊方格,然后递归填充右上角子棋盘。
如果特殊方块在左下角的子棋盘,则递归填充左下角子棋盘;否则递归填充左下角子棋盘的右上角,将右上角看做特殊方块,然后递归填充左下角子棋盘。
如果特殊方块在右下角的子棋盘,则递归填充右下角子棋盘;否则递归填充右下角子棋盘的左上角,将左上角看做特殊方块,然后递归填充右下角子棋盘。
已知条件是一个特殊棋盘上有一个特殊方格,所以算法的输入可以用来表示棋盘的大小,以原始棋盘的左上角为原点建立而为直角坐标系,用(dr,dc)表示特殊方格在棋盘中的位置。
递归方程的建立与复杂度分析:设T(k)是算法ChessBoard覆盖一个2k*2k棋盘所需时间,应用递归与分治策略可得出以下递归方程:
在这里插入图片描述
解此递归方程可得T(k)=O(4k)

算法实现

4.1 完整的代码实现



//Board.java棋盘类(棋盘初始化、算法定义以及结果的输出)
package com.company;
/**
 * @author 阿飞
 * @date 2021/9/18
 */
public class Board {
    int dr,dc,size;
    int[][] Board;
    int tile = 1;//骨牌编号从1开始

    public Board() {
    }

    public Board( int dr, int dc, int size,int[][] Board) {
        this.dr = dr;
        this.dc = dc;
        this.size = size;
        Board = new int[size][size];
    }
    public void ChessBoard(int tr, int tc, int dr, int dc, int size)throws Exception{

        if(size == 1){
            return;//size=1时,递归结束,即size=1为递归出口
        }
        int s = size/2;//分割棋盘
        int t = tile++;//L型骨牌号

        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 void printBoard(int dr,int dc,int size)throws Exception{
        try {
            ChessBoard(0,0,dr,dc,size);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for(int i=0;i<size;i++){
            for(int j=0;j<size;j++){
                System.out.print(Board[i][j]+" ");
            }
            System.out.println();
        } }}

//主函数:

package com.company;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("请输入棋盘的大小(值的要求为2的幂)");
        Scanner s = new Scanner(System.in);//输入棋盘大小
        int size = s.nextInt();
        if(size%2==0){//检验输入是否符合要求,不符合则退出,符合则继续
       	 	System.out.println("请输入特殊方格的横坐标(从1开始)");
        	Scanner X =new Scanner(System.in);
        	int x = X.nextInt();
        	System.out.println("请输入特殊方格的纵坐标(从1开始)");
        	Scanner Y =new Scanner(System.in);
        	int y = Y.nextInt();
        	Board chesssBoard = new Board(x-1,y-1,size);
        	chesssBoard.printBoard(x-1,y-1,size);
}else{
        System.out.println("输入有误!请退出!");
      }
}

运行结果截图:(结果说明:标号为0的位置就是特殊方格的位置)
在这里插入图片描述

图5.棋盘大小为8,特殊方格坐标为(3,3)的执行结果
在这里插入图片描述

图6.棋盘大小为4,特殊方格坐标为(1,1)的执行结果

4.2 关键代码说明

说明一:

if(size == 1){
            return;//size=1时,递归结束,即size=1为递归出口
        }
        int s = size/2;//分割棋盘
        int t = tile++;//L型骨牌号

        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);//覆盖其余方格
        }
        

此段代码是分别在四种情况下的不同填充策略(理论分析部分已阐述)的实现。
说明二:

Board chesssBoard = new Board(x-1,y-1,size);
        chesssBoard.printBoard(x-1,y-1,size);
Board[][]数组是从下标为0开始存储和输出的,所以当使用者输入特殊点的
坐标为(x,y)时,需要进行减1操作,才可以对应到该从1开始的坐标
说明三:
if(size%2==0){//检验输入是否符合要求,不符合则退出,符合则继续
       	 	System.out.println("请输入特殊方格的横坐标(从1开始)");
        	Scanner X =new Scanner(System.in);
        	int x = X.nextInt();
        	System.out.println("请输入特殊方格的纵坐标(从1开始)");
        	Scanner Y =new Scanner(System.in);
        	int y = Y.nextInt();
        	Board chesssBoard = new Board(x-1,y-1,size);
        	chesssBoard.printBoard(x-1,y-1,size);
}else{
        System.out.println("输入有误!请退出!");
      }
}

在进行代码设计时应将棋盘大小为2k*2k的条件考虑进去,所以需要对输入进行合法性检测,保证用户输入值为2的整数次幂。
实验的大部分都如此这般,后面的代码调试、实验总结均各有不同。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ivan陈哈哈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值