棋盘覆盖问题:Java 和 C 实现版本

一、问题描述

棋盘覆盖问题:
	1、要求在2^k * 2^k 个方格组成的棋盘中,
	2、你给定任意一个特殊点,用一种方案实现对除该特殊点的棋盘实现全覆盖。

image-20201205071946595

二、什么是:分治算法, 棋盘中如何用?

分治算法(通俗的讲):
	1、大问题,分解为小问题。
	2、分解的(小问题之间)互相不重叠
	3、小问题和大问题(是同类型问题),只是规模不一样。
image-20201205074909552

三、问题讲解:视频推荐

可以结合着看:第一个(视频)图很生动, 第二个视频(分析复杂度,很透彻), 首推(第二个)

视频一:https://www.bilibili.com/video/BV1vt4y1i7VF/?spm_id_from=333.788.videocard.5

视频二:https://www.bilibili.com/video/BV1o7411Q7GV/?spm_id_from=333.788.videocard.4

四、博客推荐

博客一:注重(理解问题)
博客二:注重(实现代码)

博客一:https://blog.csdn.net/qq_30268545/article/details/80600064?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2.control


博客二:https://blog.csdn.net/sunshine__0411/article/details/78413823?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2.control

五、代码实现C 和 Java版本

1、C版本(visual studio 2019编译器)

注意:因为visual studio 用scanf() 被认为不安全,此处用了scanf_s(), 其余编译器,修改下,即可

①、代码

#include <stdio.h>
#include<math.h>

static int tile = 1;								//L骨牌:开始的值
const int BOARD_SIZE = 64;							//棋盘的:最大值 64 * 64
static int board[BOARD_SIZE][BOARD_SIZE] = {
    
     0 };	//初始化棋盘:行BOARD_SIZE * 列BOARD_SIZE



//tr, tc起始点, dr,dc(特殊点), size棋盘行、列大小
void chess_board(int tr, int tc, int dr, int dc, int size) {
    
    
	//1、检验:size 是否为1, 若为1, 则说明(1 * 1)棋盘, 只有一个格子(该格子就是:特殊点),不用覆盖
	if (size == 1) {
    
    
		return;
	}

	//2、骨牌L存放的数字:自增
	int t = tile++;					//切记:t为作为标志当前骨牌(存放的数字),tile自增是因为:方便递归

	//3、将当前棋盘:分割成四块, 利用(分治算法), 分治算法:问题划分为(同种问题、且问题不重复)
	int sz = size / 2;				//分割后的大小为:(2^k - 1) * (2^k - 1)

	//4、判断:特殊点在,左上(分块)吗?
	if (dr < tr + sz && dc < tc + sz) {
    
    		//此时(特殊点)在:左上分块
		chess_board(tr, tc, dr, dc, sz);	//继续:分割棋盘,用L型骨牌(覆盖)
	}
	else {
    
    
		board[tr + sz - 1][tc + sz - 1] = t;		//将:特殊结点外的,毗连(左上结点)赋上骨牌值
		chess_board(tr, tc, tr + sz - 1, tc + sz - 1, sz);		//将该毗连结点,作为新的特殊点:递归
	}

	//5、判断:特殊点在,右上(分块)吗?
	if (dr < tr + sz && dc >= tc + sz) {
    
    	//此时:特殊点在(右上角块)
		chess_board(tr, tc + sz, dr, dc, sz);		//初始值的变更:总在该方格的(左上角顶)
	}
	else {
    
    
		board[tr + sz - 1][tc + sz] = t;
		chess_board(tr, tc + sz, tr + sz - 1, tc + sz, sz);
	}


	//6、判断:特殊点在,左下(分块)吗?
	if (dr >= tr + sz && dc < tc + sz) {
    
    		//此时:特殊点在(左下块)
		chess_board(tr + sz, tc, dr, dc, sz);
	}
	else {
    
    
		board[tr + sz][tc + sz - 1] = t;
		chess_board(tr + sz, tc, tr + sz, tc + sz - 1, sz);
	}

	//7、判断:特殊点在,右下(分块)吗?
	if (dr >= tr + sz && dc >= tc + sz) {
    
    		//此时:特殊点在(右下块)
		chess_board(tr + sz, tc + sz, dr, dc, sz);
	}
	else {
    
    
		board[tr + sz][tc + sz] = t;
		chess_board(tr + sz, tc + sz, tr + sz, tc + sz, sz);
	}
}

void print_chess(int size) {
    
    
	for (int i = 0; i < size; i++) {
    
    
		for (int j = 0; j < size; j++) {
    
    
			printf("%4d", board[i][j]);
		}

		printf("\n");
	}
}


//通过输入k值:获取棋盘大小
int getSize() {
    
    
	//1、请输入:棋盘大小2^k中的(k值)
	int k = 0;
	printf("请输入 k = ");
	scanf_s("%d", &k);

	//2、判断:k是否大于6 (因为我:设置了最大棋盘为 64 * 64)
	int size = 1;					//棋盘:行、列值(棋盘是一个:矩阵)
	while (true) {
    
    
		if (k < 0 || k > 6) {
    
    
			printf("您的k值有误,请输入 k = ");
			scanf_s("%d", &k);
		}
		else {
    
    
			size = pow(2, k);
			break;
		}
	}

	//3、打印棋盘大小, 并返回
	printf("您的棋盘大小为:%d * %d\n", size, size);
	return size;
}

//设置:起初,棋盘的(特殊点)的坐标(从索引0开始)
void setSpecialPot(int* x1, int* y1, int size) {
    
    
	int x;
	int y;

	while (true) {
    
    
		printf("请输入(特殊点)的 x = ");
		scanf_s("%d", &x);
		printf("请输入(特殊点)的 y = ");
		scanf_s("%d", &y);

		if (x >= 0 && x <= size - 1 && y >= 0 && y <= size - 1) {
    
    
			break;
		}
		else {
    
    
			printf("您的x, y值(越界), 请重新输入:\n");
		}
	}
	*x1 = x;
	*y1 = y;
}

int main(void) {
    
    

	//1、获取棋盘大小
	int size = getSize();

	//2、请输入:初始(特殊点)
	int x;
	int y;
	setSpecialPot(&x, &y, size);

	//3、进行棋盘覆盖
	chess_board(0, 0, x, y, size);			//覆盖棋盘


	//4、打印棋盘
	print_chess(size);

}

②、代码图

image-20201204215317865

③、运行结果

image-20201204214851802

2、Java版本(已封装, Intellij编译器)

已封装版本:看起来清爽,理解起来可能要(想一想)

①、代码

package com.yyy.test;

import java.util.Scanner;

/**
 * 分治法——棋盘覆盖问题
 * 棋盘覆盖问题:描述
 *       <1>、有一个2^k∗2^k的(方格棋盘),(只有1个)方格是黑色的,其他为白色。
 *       <2>、你的任务是用包含3个方格的L型牌覆盖所有白色方格。黑色方格不能被覆盖,
 *       <3>、且任意一个白色方格(不能同时)被(两个或更多牌)覆盖。
 */
public class ChessBoard01 {
    
    
    public int board[][];                  //棋盘:引用
    private int boardSize;                 //棋盘大小
    private int tile;                      //L骨牌:存放值
    private int SpecialX;                  //特殊方格:行索引
    private int SpecialY;                  //特殊方格:列索引

    public ChessBoard01() {
    
    

        //获取:棋盘大小
        boardSize = getBoardSize();

        //初始化:棋盘
        board = new int[boardSize][boardSize];

        //初始化:特殊方格
        initSpecial();

        //初始化:骨牌值
        tile = 1;           //骨牌值:从1开始
    }

    /**
     * 该方法:利用分治思想,大问题分解成(同一类型的:小问题), 小问题之间(没有交叉)进行(棋盘覆盖)
     * @param tr: 初始(查找点)行索引
     * @param tc:初始(查找点)列索引
     * @param dr:初始(特殊方格)行索引
     * @param dc:初始(特殊方格)列索引
     * @param initSize:初始(棋盘大小),initSize * initSize (矩阵棋盘)
     */
    public void chessBoard(int tr, int tc, int dr, int dc, int initSize){
    
    
        //1、判断:当棋盘只有(1个方格时),不用覆盖, 因为该方格,必为(特殊方格)
        if(initSize == 1){
    
    
            return;         //此时是:递归出口
        }

        //2、创建:骨牌序号(L骨牌由3个方格组成),这3个方格(值一样)
        int tileSeque = tile++;     //tile值:后置递增,是为了(递归覆盖)方便

        //3、将棋盘分4块: 大小变为 (2^k)-1  * (2^k)-1
        int size = initSize / 2;

        //4、判断:(特殊方格)是否在(左上分块)
        if(dr < tr+size && dc < tc+size){
    
           //此时:(特殊方格)在(左上分块)中
            chessBoard(tr, tc, dr, dc, size);   //直接:递归(左上分块)
        }else{
    
    
            this.board[tr+size-1][tc+size-1] = tileSeque;   //此时:特殊方格(不在,左上分块中),为小问题(生成:特殊方格)
            chessBoard(tr, tc, tr+size-1, tc+size-1, size);    //小问题:分块,覆盖
        }

        //5、判断:(特殊方格)是否在(右上分块)
        if(dr < tr+size && dc >= tc+size){
    
                  //此时:(特殊方格)在(右上分块中)
            chessBoard(tr, tc+size, dr, dc, size);  //直接:递归(右上分块)
        }else{
    
    
            this.board[tr+size-1][tc+size] = tileSeque;     //此时:特殊方格(不在,右上分块中),为小问题(生成:特殊方格)
            chessBoard(tr, tc+size, tr+size-1, tc+size, size);    //小问题:分块,覆盖
        }

        //6、判断:(特殊点)是否在(左下分块)
        if(dr >= tr+size && dc < tc+size){
    
                 //此时:特殊方格,在左下分块中
            chessBoard(tr+size, tc, dr, dc, size);  //直接:递归(左下分块)
        }else{
    
    
            this.board[tr+size][tc+size-1] = tileSeque;     //此时:特殊方格(不在,左下分块中),为小问题(生成:特殊方格)
            chessBoard(tr+size, tc, tr+size-1, tc+size-1, size);     //小问题:分块,覆盖
        }

        //7、判断:(特殊点)是否在(右下分块)
        if(dr >= tr+size && dc >= tc+size){
    
           //此时:特殊方格,在右下分块中
            chessBoard(tr+size, tc+size, dr, dc, size);   //直接:递归(右下分块)
        }else{
    
    
            this.board[tr+size][tc+size] = tileSeque;  //此时:特殊方格(不在,右下分块中),为小问题(生成:特殊方格)
            chessBoard(tr+size, tc+size, tr+size, tc+size, size);
        }
    }


    //获取:棋盘大小
    public int getBoardSize(){
    
    
        //1、设置:扫描(控制台)输入内容
        Scanner stdIn = new Scanner(System.in);

        //2、输入棋盘(2^k)*(2^k)中的:k值
        System.out.print("请输入,指数幂(整数) k = ");
        int k = stdIn.nextInt();

        //3、通过k值:获取(矩阵棋盘)大小
        int size =  (int)Math.pow(2, k);
        System.out.println("棋盘大小为:" + size + " * " + size);

        return size;
    }


    //初始化:特殊方块
    public void initSpecial(){
    
    
        Scanner stdIn = new Scanner(System.in);

        //4、输入:棋盘(特殊方格)的(索引坐标)
        System.out.print("请输入,特殊方格(行坐标) x = ");
        int x = stdIn.nextInt();

        System.out.print("请输入,特殊方格(列坐标) y = ");
        int y = stdIn.nextInt();

        while (true){
    
    
            if(x >= 0 && x <= boardSize-1 && y >= 0 && y <= boardSize-1){
    
       //检测(特殊方格)坐标是否越界
                break;                  //坐标没有越界
            }else{
    
    
                System.out.println("特殊方格,初始化坐标越界,请重新输入:");
                System.out.print("请输入,特殊方格(行坐标) x = ");
                x = stdIn.nextInt();

                System.out.print("请输入,特殊方格(列坐标) y = ");
                y = stdIn.nextInt();
            }
        }
        SpecialX = x;
        SpecialY = y;
    }

    //间接调用:覆盖棋盘
    public void chessBoardStart(){
    
    
        chessBoard(0, 0, SpecialX, SpecialY, boardSize);
    }

    //打印:打印棋盘内容
    public void printBoard(){
    
    
        for(int i = 0; i < board.length; i++){
    
    
            for(int j = 0; j < board.length; j++){
    
    
                System.out.print(board[i][j] + "\t");
            }
            System.out.println("");                     //换行
        }
    }


    //主方法
    public static void main(String[] args){
    
    
        //1、创建:棋盘对象
        ChessBoard01 board = new ChessBoard01();

        //2、进行覆盖
        board.chessBoardStart();

        //3、打印:覆盖后的棋盘内容
        board.printBoard();

    }
}

②、代码图

image-20201204221219641

③、运行结果

image-20201204220347373

3、Java版本(未封装)

未封装版本:理解起来可能更简单

①、代码

package com.yyy.test;

import java.util.Scanner;

/**
 * 分治法——棋盘覆盖问题
 * 棋盘覆盖问题:描述
 *       <1>、有一个2^k∗2^k的(方格棋盘),(只有1个)方格是黑色的,其他为白色。
 *       <2>、你的任务是用包含3个方格的L型牌覆盖所有白色方格。黑色方格不能被覆盖,
 *       <3>、且任意一个白色方格(不能同时)被(两个或更多牌)覆盖。
 */
public class ChessBoard {
    
    
    public int board[][];      //棋盘:大小
    private int tile;           //L骨牌:存放值

    public ChessBoard(int size, int tile) {
    
    
        this.board = new int[size][size];          //初始化棋盘大小:为2^k * 2^k 大小
        this.tile = tile;                     //初始化L骨牌:存放值
    }

    /**
     * 该方法:利用分治思想,大问题分解成(同一类型的:小问题), 小问题之间(没有交叉)进行(棋盘覆盖)
     * @param tr: 初始(查找点)行索引
     * @param tc:初始(查找点)列索引
     * @param dr:初始(特殊方格)行索引
     * @param dc:初始(特殊方格)列索引
     * @param initSize:初始(棋盘大小),initSize * initSize (矩阵棋盘)
     */
    public void chessBoard(int tr, int tc, int dr, int dc, int initSize){
    
    
        //1、判断:当棋盘只有(1个方格时),不用覆盖, 因为该方格,必为(特殊方格)
        if(initSize == 1){
    
    
            return;         //此时是:递归出口
        }

        //2、创建:骨牌序号(L骨牌由3个方格组成),这3个方格(值一样)
        int tileSeque = tile++;     //tile值:后置递增,是为了(递归覆盖)方便

        //3、将棋盘分4块: 大小变为 (2^k)-1  * (2^k)-1
        int size = initSize / 2;

        //4、判断:(特殊方格)是否在(左上分块)
        if(dr < tr+size && dc < tc+size){
    
           //此时:(特殊方格)在(左上分块)中
            chessBoard(tr, tc, dr, dc, size);   //直接:递归(左上分块)
        }else{
    
    
            this.board[tr+size-1][tc+size-1] = tileSeque;  //此时:特殊方格(不在,左上分块中),为小问题(生成:特殊方格)
            chessBoard(tr, tc, tr+size-1, tc+size-1, size);    //小问题:分块,覆盖
        }

        //5、判断:(特殊方格)是否在(右上分块)
        if(dr < tr+size && dc >= tc+size){
    
                  //此时:(特殊方格)在(右上分块中)
            chessBoard(tr, tc+size, dr, dc, size);  //直接:递归(右上分块)
        }else{
    
    
            this.board[tr+size-1][tc+size] = tileSeque;   //此时:特殊方格(不在,右上分块中),为小问题(生成:特殊方格)
            chessBoard(tr, tc+size, tr+size-1, tc+size, size);    //小问题:分块,覆盖
        }

        //6、判断:(特殊点)是否在(左下分块)
        if(dr >= tr+size && dc < tc+size){
    
                 //此时:特殊方格,在左下分块中
            chessBoard(tr+size, tc, dr, dc, size);  //直接:递归(左下分块)
        }else{
    
    
            this.board[tr+size][tc+size-1] = tileSeque;  //此时:特殊方格(不在,左下分块中),为小问题(生成:特殊方格)
            chessBoard(tr+size, tc, tr+size-1, tc+size-1, size);     //小问题:分块,覆盖
        }

        //7、判断:(特殊点)是否在(右下分块)
        if(dr >= tr+size && dc >= tc+size){
    
           //此时:特殊方格,在右下分块中
            chessBoard(tr+size, tc+size, dr, dc, size);   //直接:递归(右下分块)
        }else{
    
    
            this.board[tr+size][tc+size] = tileSeque;  //此时:特殊方格(不在,右下分块中),为小问题(生成:特殊方格)
            chessBoard(tr+size, tc+size, tr+size, tc+size, size);
        }
    }

    //打印:打印棋盘内容
    public void printBoard(){
    
    
        for(int i = 0; i < board.length; i++){
    
    
            for(int j = 0; j < board.length; j++){
    
    
                System.out.print(board[i][j] + "\t");
            }
            System.out.println("");                     //换行
        }
    }

    //获取棋盘大小

    //主方法
    public static void main(String[] args){
    
    
        //1、设置:扫描(控制台)输入内容
        Scanner stdIn = new Scanner(System.in);

        //2、输入棋盘(2^k)*(2^k)中的:k值
        System.out.print("请输入,指数幂(整数) k = ");
        int k = stdIn.nextInt();

        //3、通过k值:获取(矩阵棋盘)大小
        int size =  (int)Math.pow(2, k);
        System.out.println("棋盘大小为:" + size + " * " + size);

        //4、输入:棋盘(特殊方格)的(索引坐标)
        System.out.print("请输入,特殊方格(行坐标) x = ");
        int x = stdIn.nextInt();

        System.out.print("请输入,特殊方格(列坐标) y = ");
        int y = stdIn.nextInt();

        while (true){
    
    
            if(x >= 0 && x <= size-1 && y >= 0 && y <= size-1){
    
       //检测(特殊方格)坐标是否越界
                break;                  //坐标没有越界
            }else{
    
    
                System.out.println("特殊方格,初始化坐标越界,请重新输入:");
                System.out.print("请输入,特殊方格(行坐标) x = ");
                x = stdIn.nextInt();

                System.out.print("请输入,特殊方格(列坐标) y = ");
                y = stdIn.nextInt();
            }
        }

        //5、创建:棋盘对象
        ChessBoard board = new ChessBoard(size, 1);

        //6、进行覆盖
        board.chessBoard(0, 0, x, y, size);

        //7、打印:覆盖后的棋盘内容
        board.printBoard();

    }
}

②、代码图

image-20201204221456907

③、运行结果

image-20201204221616675

4、总结:复杂度

image-20201205075209762

猜你喜欢

转载自blog.csdn.net/weixin_44537669/article/details/110676529