递归分治 --- 例题2.棋盘覆盖

递归分治 — 例题2.棋盘覆盖


一.问题描述

在一个2^k x 2k个方格组成的棋盘中,恰有一方格残缺.残缺方格的位置有2(2k)种。故对任何k≥0,残缺棋盘有2^(2k)种.
在棋盘覆盖问题中,要求用L型骨牌覆盖残缺棋盘上的所有方格且任何2个L型,骨牌不得重叠覆盖.
2^k x 2k的棋盘覆盖中,用到的骨牌数为(4k -1)/3.

二.解题思路

使用分治算法,可以设计解棋盘覆盖问题的一个简捷算法.

当k>0时,将2^k x 2k棋盘分割为**4个2(k-1) x 2^(k-1)子棋盘**, 残缺方格必位于4个子棋盘之一.

其余3个子棋盘中无残缺方格为此将剩余3棋盘转化为残缺棋盘. 用一个L型骨牌覆盖这3个较小棋盘的结合处. 这3个子棋盘上被L型骨牌覆盖的方格就成为该棋盘上的残缺方格,原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为1 x 1棋盘.
递归分治 --- 例题2.棋盘覆盖

代码实现中,我们用一个二位整型数组Board表示棋盘,Board[0] [0]是棋盘左上角方格.
tile是算法中的一个全局整型变量,用来表示L型骨牌的编号,其初始值为0.算法的输入参数是:

  • tr: 棋盘左上角的行号 tc:棋盘左上角的列号
  • dr: 特殊方格所在的行号 dr: 特殊方格所在的列号
  • size: size = 2^k, 表示棋盘的规格
递归分治 --- 例题2.棋盘覆盖

代码如下:

#include<bits/stdc++.h>
using namespace std;
int **Board;
int tile=1; //(0 by default)
void ChessBoard(int tr, int tc, int dr, int dc, int size)
{
    //tr:棋盘左上角方格的行号  tc:棋盘左上角方格列号
    //dr:特殊方格所在的行号    dc:特殊方格所在的列号
    //size:棋盘的行数或列数
    if(size==1) 
    {
        cout<<"size==1,return!"<<endl;
        return;
    }
    int t = tile++, //骨牌编号,每次加1,统一骨牌编号,特殊方格标号为0
    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放在右下角,将此象限转化为特殊棋盘
        cout<<"此时t:"<<t<<" 填充格坐标位于:("<<tr+s-1<<","<<tc+s-1<<")"<<endl;
        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;
        cout<<"此时t:"<<t<<" 填充格坐标位于:("<<tr+s-1<<","<<tc+s<<")"<<endl;
        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;
        cout<<"此时t:"<<t<<" 填充格坐标位于:("<<tr+s<<","<<tc+s-1<<")"<<endl;
        ChessBoard(tr+s, tc, tr+s, tc+s-1, s);  
    }
    // 覆盖右下象限
    if(dr>=tr+s && dr>=tc+s)
        ChessBoard(tr+s, tc+s, dr, dc, s);
    else
    {
        Board[tr+s][tc+s] = t;
        cout<<"此时t:"<<t<<" 填充格坐标位于:("<<tr+s<<","<<tc+s<<")"<<endl;
        ChessBoard(tr+s, tc+s, tr+s, tc+s, s);
    }
}
void OutputBoard(int size)
{
    for(int i=0; i<size; ++i)
    {
        for(int j=0; j<size; ++j)
        {
            cout.width(5);
            cout<<Board[i][j];
        }
        cout<<endl;
    }
}

int main()
{
    // size:棋盘的行数或列数
    int size, i;
    cout<<"Lines or Columns of chessBoard: ";
    cin>>size;
    Board = new int*[size];
    for(i=0; i<size; ++i)
    {
        Board[i] = new int[size];
        for(int j=0; j<size; ++j)
            Board[i][j] = 0;
    }

    //dr, dc:特殊方格所在的行号和列号
    int dr, dc;
    cout<<"Line and column NO. of defective grid: ";  //输入特殊格坐标
    cin>>dr>>dc;
    ChessBoard(0, 0, dr, dc, size);
    OutputBoard(size);
    for(int i=0; i<size; ++i)
        delete[] Board[i];
    delete[] Board;
    system("pause");
    return 0;
}

下面图片是自己的课堂作业以及最终用程序跑出来的结果:
测试样例:棋盘大小 8x8, 特殊格坐标(3, 5)
递归分治 --- 例题2.棋盘覆盖 紫色的序号表示填充的顺序,之后通过程序也可以验证.
(好像写错一个,ε=ε=ε=┏(゜ロ゜;)┛)

递归分治 --- 例题2.棋盘覆盖递归分治 --- 例题2.棋盘覆盖

这里有一个问题,也是上课时老师问过我的一个问题,就是为什么当递归回来的时候,骨牌的编号还是保持一致的呢.
这里面我们知道递归是一直往里走,每一层的骨牌编号+1后保存在系统栈中,当遇到终止条件return回来或者是找遍当前层所有情况return之后,自动弹栈!
这就是为什么可以保证每一层的骨牌编号都是一致的,这个毋庸置疑!

参考毕方明老师《算法设计与分析》课件.

欢迎大家访问个人博客网站—乔治的编程小屋,和我一起为大厂offer努力!

上一篇:Navicat 连接MySql服务报错1251(本地数据库)


下一篇:旋转打印矩阵