演示程序截图
问题描述
- 残缺棋盘(defective chessboard):是一个有 2 k × 2 k 2^k×2^k 2k×2k 个方格的棋盘,其中恰有一个方格残缺。对于任意 k k k,恰好存在 2 2 k 2^{2k} 22k 种不同的残缺棋盘。
- 在残缺棋盘中,要求用三格板(triominoes)覆盖残缺棋盘。在覆盖中,任意两个三格板不能重叠,任意一个三格板不能覆盖残缺方格,但三格板必须覆盖其他所有方格。
基本要求
- 输入棋盘大小和残缺方格的位置,输出覆盖后的棋盘。
- 输出棋盘时要着色,共享同一边界的覆盖应着不同的颜色。棋盘是平面图,要求使用最少的颜色覆盖着色
问题分析
(1)创建残缺棋盘——初始化模块
按照用户输入,创建残缺棋盘对象
ChessBoard(int k,int dx,int dy);
(2)确定三格板的最小颜色编号——着色模块
//判断坐标是否越界
bool Range(int x,int y);
//确定当前三格板的最小颜色编号
void GetNo(int x,int y,bool vis[]);
//三格板着色
void Color(int sx,int sy,int dx,int dy,int size);
遍历当前要覆盖的三格板的相邻方格,确定三格板的最小颜色编号
(3)三格板覆盖残缺棋盘——算法模块
void Dye(int sx,int sy,int dx,int dy,int size);
void Cover();
调用类内算法,实现三格板覆盖残缺棋盘
(4)输出覆盖结果——输出模块
void Output();
(5)棋盘可视化——可视化模块
void paintEvent(QPaintEvent *);
设计思路
(1)创建棋盘:ChessBoard(int k,int dx,int dy);
首先进行赋值操作,完成对棋盘级数以及残缺点所在坐标的初始化;然后动态申请内存空间创建二维数组,最后完成初始化棋盘(二维数组)的操作。
(2)析构棋盘:~ChessBoard();
首先释放二维数组的第二维所申请的内存空间,最后释放第一维所申请的内存空间。
(3)确定三格板的最小颜色编号——着色模块:
void Color(int sx,int sy,int dx,int dy,int size);
该模块的核心思想是搜索相邻方格,新建一个bool类型的vis数组,来对搜索过程中遇到的颜色编号进行标记,最后遍历vis数组,从中取得未被标记的最小编号作为当前三格板的颜色编号,此时的颜色编号是最小的。
(4)三格板覆盖棋盘:void Dye(int sx,int sy,int dx,int dy,int size);
该模块的核心思想是分治,将覆盖整个棋盘这个原问题分解为独立且与原问题相似的子问题进行求解,具体实现思路如下:
先将棋盘均分成四部分,判定出没有残缺块的三个区域,然后在当前棋盘的中心位置放置三格板覆盖没有残缺块的三个区域的靠近中心的位置,将被覆盖的区域看作残缺块,这样四个区域均可转换为与原问题相同的残缺棋盘问题,分而治之,直至处理完全部棋盘,算法结束
(5)输出棋盘:void Output();
遍历棋盘(二维数组),格式化输出所对应下标的值
(6)棋盘可视化:void paintEvent(QPaintEvent * );
本模块使用Qt C++中的QPixmap类来绘制棋盘,首先指定绘图设备并填充背景,然后新建一个QPen类对象执行绘制棋盘的操作,棋盘着色的实现,则是首先新建一个QBrush类对象,然后调用QRandomGenerator类的generator函数接口来生成特定范围内的随机数以获取相应的RGB值,并根据随机生成的RGB值构造QColor类对象数组,最后根据存储棋盘的二维数组的相关位置与值的大小来设置画笔的绘图位置与颜色属性来完成颜色的填充。
代码实现
ChessBoard.h
#ifndef CHESSBOARD_H
#define CHESSBOARD_H
//坐标
struct Coordinate{
int x,y;
Coordinate(){}
Coordinate(int xx,int yy):x(xx),y(yy){}
};
class ChessBoard{
public:
//构造函数
ChessBoard(int k,int dx,int dy);
//析构函数
~ChessBoard();
//覆盖以(sx,sy)为左上角,大小为size的棋盘
void Dye(int sx,int sy,int dx,int dy,int size);
//覆盖
void Cover();
//输出棋盘
void Output();
//判断坐标是否越界
bool Range(int x,int y);
//确定当前三格板的最小颜色编号
void GetNo(int x,int y,bool vis[]);
//三格板着色
void Color(int sx,int sy,int dx,int dy,int size);
//private:
int **a; //存储棋盘信息
int Size; //棋盘边长
int default_x; //残缺点行号
int default_y; //残缺点列号
int num; //使用三格板的个数
int tot; //颜色个数
};
#endif // CHESSBOARD_H
ChessBoard.cpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include "ChessBoard.h"
using namespace std;
//位移
int d[][2]={{0,1},{0,-1},{1,0},{-1,0}};
//构造函数
ChessBoard::ChessBoard(int k,int dx,int dy){
Size=pow(2,k);
default_x=dx;
default_y=dy;
tot=num=0;
//动态申请二维数组
a=new int*[Size];
for(int i=0;i<Size;i++)
a[i]=new int[Size];
//初始化数组
for(int i=0;i<Size;i++)
for(int j=0;j<Size;j++)
a[i][j]=0;
//设置残缺点
a[dx][dy]=-1;
}
//析构函数
ChessBoard::~ChessBoard(){
//释放内存空间
for(int i=0;i<Size;i++)
delete[] a[i];
delete []a;
}
//判断坐标是否越界
bool ChessBoard::Range(int x,int y){
if(x<0||x>=Size||y<0||y>=Size||(x==default_x&&y==default_y))
return false;
return true;
}
//确定当前三格板的最小颜色编号
void ChessBoard::GetNo(int x,int y,bool vis[]){
for(int i=0;i<4;i++){
int dx=x+d[i][0],dy=y+d[i][1];
if(Range(dx,dy)){
vis[a[dx][dy]]=true;
}
}
}
//三格板染色
void ChessBoard::Color(int sx,int sy,int dx,int dy,int size){
Coordinate tmp[10];
tmp[0]=Coordinate(sx+size-1,sy+size-1);
tmp[1]=Coordinate(sx+size-1,sy+size);
tmp[2]=Coordinate(sx+size,sy+size-1);
tmp[3]=Coordinate(sx+size,sy+size);
bool vis[tot+10];
memset(vis,0,sizeof(vis));
//搜索周围区域
for(int i=0;i<4;i++){
if(!(tmp[i].x==dx&&tmp[i].y==dy)){
GetNo(tmp[i].x,tmp[i].y,vis);
}
}
int No=0;
//确定三格板的最小颜色编号
for(int i=0;i<tot+10;i++){
if(!vis[i]){
No=i;
break;
}
}
tot=max(tot,No);
//染色
for(int i=0;i<4;i++){
if(!(tmp[i].x==dx&&tmp[i].y==dy)){
a[tmp[i].x][tmp[i].y]=No;
}
}
}
void ChessBoard::Dye(int sx,int sy,int dx,int dy,int size){
//覆盖完毕
if(size<2)
return;
num++;
//从中间分割棋盘
size/=2;
// 覆盖左上角子棋盘
if (dx<sx+size&&dy<sy+size){
Color(sx,sy,sx+size-1,sy+size-1,size);
Dye(sx,sy,dx,dy,size);
}
else
Dye(sx,sy,sx+size-1,sy+size-1,size);
//覆盖右上角子棋盘
if (dx<sx+size&&dy>=sy+size){
Color(sx,sy,sx+size-1,sy+size,size);
Dye(sx,sy+size,dx,dy,size);
}
else
Dye(sx,sy+size,sx+size-1,sy+size,size);
//覆盖左下角子棋盘
if (dx>=sx+size&&dy<sy+size){
Color(sx,sy,sx+size,sy+size-1,size);
Dye(sx+size,sy,dx,dy,size);
}
else
Dye(sx+size,sy,sx+size,sy+size-1,size);
// 覆盖右下角子棋盘
if (dx>=sx+size&&dy>=sy+size){
Color(sx,sy,sx+size,sy+size,size);
Dye(sx+size,sy+size,dx,dy,size);
}
else
Dye(sx+size,sy+size,sx+size,sy+size,size);
}
//覆盖
void ChessBoard::Cover(){
Dye(0,0,default_x,default_y,Size);
}
//输出
void ChessBoard::Output(){
for(int i=0;i<Size;i++){
for(int j=0;j<Size;j++){
cout<<a[i][j]<<" ";
}
cout<<endl;
}
}
Mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include "ChessBoard.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
ChessBoard *test;
MainWindow(QWidget *parent = nullptr);
~MainWindow();
QPixmap *map; // 绘制棋盘
qreal sizeW; // 单个方格的宽
qreal sizeH; // 单个方格的高
bool flag_1; //create
bool flag_2; //color
void paintEvent(QPaintEvent *);
private slots:
void on_pushButton_2_clicked();
void on_pushButton_clicked();
void on_pushButton_3_clicked();
void on_pushButton_4_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
Mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "ChessBoard.h"
#include <QPainter>
#include <QRandomGenerator>
#include <QMouseEvent>
#include <QString>
#include <cmath>
//判断用户输入是否为整数
bool isDigitStr(QString src){
QByteArray ba = src.toLatin1();
const char *s = ba.data();
while(*s && *s>='0' && *s<='9') s++;
if (*s){
return false;
}
else{
return true;
}
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle("BrokenChessBoard");
this->setFixedSize(QSize(1230, 980)); // 固定界面的大小
test=new ChessBoard(2,0,0); // 初始化棋盘
//flag_1:create
//flag_2:color
flag_1=flag_2=false;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::paintEvent(QPaintEvent *)
{
// 初始化棋盘
map = new QPixmap(900, 900); // 初始化QPixmap的大小
map->fill(Qt::white); // 填充背景色为白色
QPainter painter(map); // 指定绘图设备为map
// 创建一个画笔
QPen pen;
pen.setWidth(1);
pen.setColor(Qt::black);
pen.setStyle(Qt::SolidLine);
painter.setPen(pen); // 设置画笔
sizeH = qreal(map->height()) / test->Size;
sizeW = qreal(map->width()) / test->Size;
// 绘制棋盘
for (int i = 1; i <= test->Size+1; i++)
// 竖线
painter.drawLine(QLineF(i*sizeW, 0, i*sizeW, map->height()));
for (int i = 1; i <= test->Size+1; i++)
// 横线
painter.drawLine(QLineF(0, i*sizeH, map->width(), i*sizeH));
// 绘制残缺方格
// 设置一个画刷
QBrush brush;
brush.setColor(QColor(0, 0, 0));
brush.setStyle(Qt::SolidPattern);
painter.setBrush(brush);
QRect rect(test->default_y*sizeW, test->default_x*sizeH, sizeW, sizeH);
if (flag_1)
painter.drawRect(rect);
// flag为true,表示“覆盖棋盘”
if (flag_2)
{
QRandomGenerator generator(10);
int nums = (test->Size*test->Size-1)/3 + 1;
QColor colors[nums];
colors[0] = QColor(0, 0, 0); // 残缺棋盘的数字0,为黑色
for (int i = 1; i < nums; i++)
colors[i] = QColor(generator.bounded(80, 255), generator.bounded(80, 255),
generator.bounded(60, 255));
for (int i = 0; i < test->Size; i++)
{
for (int j = 0; j<test->Size; j++)
{
// 填充颜色
int temp = test->a[i][j];
QBrush brusher;
brusher.setColor(colors[temp]);
brusher.setStyle(Qt::SolidPattern);
painter.setBrush(brusher);
rect.setRect(j*sizeW, i*sizeH, sizeW, sizeH);
painter.drawRect(rect);
// 填写数字
painter.drawText(QPoint(j*sizeW+sizeW/2, i*sizeH+sizeH/2), QString::number(temp));
}
}
flag_2 = false;
}
// 绘制
QPainter painterAll(this); // 指定绘图设备为界面
painterAll.drawPixmap(QPoint(300, 50), *map);
}
void MainWindow::on_pushButton_2_clicked()
{
close();
}
//Create
void MainWindow::on_pushButton_clicked()
{
// 初始化棋盘
flag_1 = true;
QString kk=ui->lineEdit->text();
QString dr=ui->lineEdit_2->text();
QString dc=ui->lineEdit_3->text();
if(!isDigitStr(kk)||!isDigitStr(dr)||!isDigitStr(dc)){
ui->label_4->setText("Error:The input must be integer!");
return;
}
int k = kk.toInt();
int defectRow = dr.toInt()-1;
int defectColumn = dc.toInt()-1;
delete test; // 清空原来的棋盘
int sz=pow(2,k);
if(k<=0||defectRow<=0||defectColumn<=0){
ui->label_4->setText("Error:The input must be positive integer!");
return;
}
if(defectRow>sz||defectColumn>sz){
ui->label_4->setText("Error:The input is out of range!");
return;
}
test = new ChessBoard(k, defectRow, defectColumn);
//绘制棋盘
this->repaint();
QString res="Successfully Create ChessBoard!\n";
res+="The Size of the ChessBoard is ";
res+=QString::number(sz);
res+="\n";
res+="The imperfect coordinates is (";
res+=dr;res+=", ";res+=dc;res+=").\n";
ui->label_4->setText(res);
}
//Color
void MainWindow::on_pushButton_3_clicked()
{
flag_2=true;
test->Cover();
this->repaint();
ui->label_4->setText("Successfully Color ChessBoard!");
}
//Reset
void MainWindow::on_pushButton_4_clicked()
{
delete test;
flag_1=false;
test=new ChessBoard(2,0,0);
this->repaint();
ui->label_4->setText("Successfully Reset ChessBoard!");
}
Main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
QT 画图事件参考了这篇 blog
https://blog.csdn.net/weixin_43360801/article/details/104169366