数据结构——残缺棋盘覆盖(问题+算法+UI界面)

残缺棋盘覆盖问题(问题+算法+UI界面)

总述:这是大二下学期的数据结构课程设计,假期花了12天学习QT(《Qt 5.9C++开发指南》),接着花了1天时间完成了这个课程设计,代码在下面的连接中
百度网盘链接:
链接:https://pan.baidu.com/s/1RdlO2fgefQIRixebHlr_Ow
提取码:95mh

一.残缺棋盘问题

残缺棋盘是一个有 2 k × 2 k 2^{k}×2^{k} 个方格的棋盘,其中恰有一个方格残缺,如下图所示。在这里插入图片描述残缺棋盘的问题要求用三格板覆盖残缺棋盘。在此覆盖中两个三格板不能重叠,三格板不能覆盖残缺方格,但必须覆盖其他所有的方格。下图就是四种方向的三格板。
在这里插入图片描述
现在给出一个随机的残缺棋盘,用上述四种三格板覆盖,给出动态求解过程。

二.算法

使用分而治之法,可以很好地解决残缺棋盘问题。把 2 k × 2 k 2^{k}×2^{k} 的残缺棋盘实例划分成为较小的残缺棋盘实例。一个自然的划分结果是4个 2 k 1 × 2 k 1 2^{k-1}×2^{k-1} 棋盘,如下图所示。
在这里插入图片描述
注意,原来的 2 k × 2 k 2^{k}×2^{k} 棋盘仅有一个残缺方格,因此划分之后,在4个小棋盘中仅仅有一个棋盘存在残缺方格。首先覆盖残缺方格的小棋盘。然后把剩下3个小棋盘转变为残缺棋盘,为此,将一个三格板放置由这3个小棋盘形成的角上,如下图所示。
在这里插入图片描述
其中,原 2 k × 2 k 2^{k}×2^{k} 棋盘的残缺方格位于左上角的 2 k 1 × 2 k 1 2^{k-1}×2^{k-1} 棋盘。递归地使用这种分割技术。当棋盘的大小减为 1 × 1 1×1 时,递归过程终止。此时的 1 × 1 1×1 棋盘仅仅包含一个方格且为残缺方格,无需覆盖三格板。
完整代码:
头文件

#ifndef ALGORICODE_H
#define ALGORICODE_H


class AlgoriCode
{
public:
    AlgoriCode(int k, int defectRow, int defectColumn);
    ~AlgoriCode();
    void coverBoard();
    int **board; // 棋盘
    int size; // 棋盘大小
    int defectRow, defectColumn; // 残缺方格的位置(defectRow, defectColumn)

private:
    int tile; // 当前使用的三格版
    void tileBoard(int topRow, int topColumn, int defectRow, int defectColumn, int size);

};

#endif // ALGORICODE_H

源文件

#include "algoricode.h"
#include <cmath>

AlgoriCode::AlgoriCode(int k, int defectRow, int defectColumn)
{
    tile = 1; 
    size = int(exp2(k));
    this->defectRow = defectRow;
    this->defectColumn = defectColumn;
    // 初始化二维数组
    board = new int*[size];
    for (int i = 0; i < size; i++)
        board[i] = new int[size];
    for (int i = 0; i < size; i++)
        for (int j = 0; j < size; j++)
            board[i][j] = 0;
}

AlgoriCode::~AlgoriCode()
{
    // 释放二维数组所用内存空间
    for (int i = 0; i < size; i++)
        delete [] board[i];
    delete [] board;
}

void AlgoriCode::coverBoard()
{
    tileBoard(0, 0, defectRow, defectColumn, size);
}

void AlgoriCode::tileBoard(int topRow, int topColumn, int defectRow, int defectColumn, int size)
{
    // topRow 表示棋盘左上角方格的行号
    // topColumn 表示棋盘左上角方格的列号
    // defectRow 表示残缺方格的行号
    // defectColumn 表示残缺方格的列号
    // size 表示残缺棋盘的边长
    if (size == 1)
        return;

    int tileToUse = tile++; // 表示当前所使用的三格板
    int quadrantSize = size / 2; // 表示分割后小棋盘的边长

    // 覆盖左上角象限(第二象限)
       if (defectRow < topRow + quadrantSize &&
           defectColumn < topColumn + quadrantSize)
          // 残缺方格属于这个象限
          tileBoard(topRow, topColumn, defectRow, defectColumn,
                    quadrantSize);
       else
       {// 这个象限无残缺方格
          // 在右下角放置一个三格板
          board[topRow + quadrantSize - 1][topColumn + quadrantSize - 1]
               = tileToUse;
          tileBoard(topRow, topColumn, topRow + quadrantSize - 1,
                    topColumn + quadrantSize - 1, quadrantSize);
       }

       // 覆盖右上角象限(第一象限)
       if (defectRow < topRow + quadrantSize &&
           defectColumn >= topColumn + quadrantSize)
          // 残缺方格属于这个象限
          tileBoard(topRow, topColumn + quadrantSize, defectRow,
                    defectColumn, quadrantSize);
       else
       {// 这个象限无残缺方格
           // 在左下角放置一个三格板
          board[topRow + quadrantSize - 1][topColumn + quadrantSize]
               = tileToUse;
          tileBoard(topRow, topColumn + quadrantSize, topRow +
                 quadrantSize - 1, topColumn + quadrantSize, quadrantSize);
        }

       // 覆盖左下角象限(第三象限)
       if (defectRow >= topRow + quadrantSize &&
           defectColumn < topColumn + quadrantSize)
          // 残缺方格属于这个象限
          tileBoard(topRow + quadrantSize, topColumn, defectRow,
                    defectColumn, quadrantSize);
       else
       {// place a tile in top-right corner
          board[topRow + quadrantSize][topColumn + quadrantSize - 1]
               = tileToUse;
          tileBoard(topRow + quadrantSize, topColumn, topRow + quadrantSize,
                    topColumn + quadrantSize - 1, quadrantSize);
       }

       // 覆盖右下角象限(第四象限)
       if (defectRow >= topRow + quadrantSize &&
           defectColumn >= topColumn + quadrantSize)
          // 残缺方格属于这个象限
          tileBoard(topRow + quadrantSize, topColumn + quadrantSize,
                    defectRow, defectColumn, quadrantSize);
       else
       {// 这个象限无残缺方格
           // 在左上角放置一个三格板
          board[topRow + quadrantSize][topColumn + quadrantSize]
               = tileToUse;
          tileBoard(topRow + quadrantSize, topColumn + quadrantSize,
              topRow + quadrantSize, topColumn + quadrantSize, quadrantSize);
        }
}

三.UI界面

先看一下总体效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个才是重点,上面那个算法,很快就能搞完,不过这个我就花了很多时间
Qt版本:5.14.0
Qt Creator版本:4.11.0(Community)
第一步:File => New File or Project => 选择Application,再选择Qt Widgets Application,创建一个项目“BrokenChessBoard”。
在这里插入图片描述
第二步:选择基类QMainWindow,类名称为“ChessBoard”。
在这里插入图片描述
OK,现在项目创建完了
看一下项目的结构
在这里插入图片描述

chessboard.h头文件

#ifndef CHESSBOARD_H
#define CHESSBOARD_H

#include <QMainWindow>
#include <algoricode.h>
#include <QLabel>

QT_BEGIN_NAMESPACE
namespace Ui { class ChessBoard; }
QT_END_NAMESPACE

class ChessBoard : public QMainWindow
{
    Q_OBJECT

private:
    AlgoriCode *code;
	
	// 状态栏的三个QLabel
    QLabel *Label_pos;
    QLabel *Label_pos_row;
    QLabel *Label_pos_col;

    QPixmap *map; // 在这里绘制棋盘
    qreal sizeW; // 单个方格的宽
    qreal sizeH; // 单个方格的高
    bool flag; // “覆盖棋盘”的标志
    bool cap; // “还原棋盘”的标志

    void paintEvent(QPaintEvent *);

public:
    ChessBoard(QWidget *parent = nullptr);
    ~ChessBoard();

private slots:

    void on_Button_build_clicked(); // “初始化棋盘”按钮的槽函数
    void on_Button_cure_clicked(); // “覆盖棋盘”按钮的槽函数
    void on_Button_solve_clicked(); // “还原棋盘”按钮的槽函数

private:
    Ui::ChessBoard *ui;
};
#endif // CHESSBOARD_H

chessboard.cpp源文件

#include "chessboard.h"
#include "ui_chessboard.h"
#include "algoricode.h"
#include <QPainter>
#include <QRandomGenerator>
#include <QMouseEvent>

ChessBoard::ChessBoard(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::ChessBoard)
{
    ui->setupUi(this);
    this->setWindowTitle("残缺棋盘覆盖");
    this->setFixedSize(QSize(1300, 900)); // 固定界面的大小
    this->setWindowIcon(QIcon(":/images/BlackHole.ico"));
    code = new AlgoriCode(2, 0, 0);
    flag = false;
    cap = false;

    // 开始时设置为不可用,“初始化棋盘”后,设置为可用
    ui->Button_solve->setEnabled(false);
    ui->Button_cure->setEnabled(false);

    // 创建状态栏标签
    // 这个目前还没实现,我的想法是根据当前鼠标光标的位置实时显示当前方格的行号和列号
    Label_pos = new QLabel("当前方格坐标");
    Label_pos->setMinimumWidth(150);
    ui->statusbar->addWidget(Label_pos);

    Label_pos_col = new QLabel("行坐标:");
    Label_pos_col->setMinimumWidth(150);
    ui->statusbar->addWidget(Label_pos_col);

    Label_pos_row = new QLabel("列坐标:");
    Label_pos_row->setMinimumWidth(150);
    ui->statusbar->addWidget(Label_pos_row);
}

ChessBoard::~ChessBoard()
{
    delete ui;
}

void ChessBoard::paintEvent(QPaintEvent *)
{
    // 初始化棋盘
    map = new QPixmap(this->width()-450, this->height()-90); // 初始化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()) / code->size;
    sizeW = qreal(map->width()) / code->size;
    // 绘制棋盘
    for (int i = 0; i <= code->size; i++)
        // 竖线
        painter.drawLine(QLineF(i*sizeW, 0, i*sizeW, map->height()));
    for (int i = 0; i <= code->size; 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(code->defectColumn*sizeW, code->defectRow*sizeH, sizeW, sizeH);
    if (cap)
        painter.drawRect(rect);

    // flag为true,表示按下了“覆盖棋盘”
    if (flag)
    {
        QRandomGenerator generator(10);
        int nums = (code->size*code->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 < code->size; i++)
        {
            for (int j = 0; j < code->size; j++)
            {
                // 填充颜色
                int temp = code->board[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 = false;
    }
    // 绘制所有的一切
    QPainter painterAll(this); // 指定绘图设备为界面,在界面上绘制map
    painterAll.drawPixmap(QPoint(25, 50), *map);
}

void ChessBoard::on_Button_build_clicked()
{
    // “初始化棋盘”按下后,初始化算法和棋盘
    // 初始化算法
    cap = true;
    int k = ui->Spin_input->value();
    int defectRow = ui->Spin_defectRow->value();
    int defectColumn = ui->Spin_defectColumn->value();
    delete code; // 清空原来的code
    code = new AlgoriCode(k, defectRow-1, defectColumn-1);
    ui->Button_build->setEnabled(false);
    ui->Button_solve->setEnabled(true);
    ui->Button_cure->setEnabled(true);
    // 更新棋盘
    this->repaint();
}

void ChessBoard::on_Button_solve_clicked()
{
    // “覆盖棋盘”按下后,用三格版覆盖棋盘
    // 三格板的颜色,可以通过随机数,指定从1开始到(size-1)/3的随机数
    // 通过生成的随机数确定颜色
    flag = true; // 表示更新棋盘时,将会画上所有三格板
    code->coverBoard(); // 调用覆盖棋盘的核心代码
    ui->Button_build->setEnabled(false);
    ui->Button_solve->setEnabled(false);
    ui->Button_cure->setEnabled(true);
    this->repaint();
}

void ChessBoard::on_Button_cure_clicked()
{
    // “还原棋盘”按下后,还原棋盘到初始情况
    delete code;
    cap = false;
    code = new AlgoriCode(2, 0, 0);
    ui->Button_build->setEnabled(true);
    ui->Button_solve->setEnabled(false);
    ui->Button_cure->setEnabled(false);
    this->repaint();
}

最后,说一下我的这个程序的4个问题

  1. 多次运行后会产生下面的“问题”,Application Output中会输出以下的语句,界面会如下面这张图所示,显示不出棋盘并且无法继续运行。我尝试解决,但未成功。在这里插入图片描述在这里插入图片描述
  2. 菜单栏的功能没有实现,我的想法是,点击会出现一个窗口,介绍算法,或者提供截图功能、保存功能、打开功能。未尝试。
  3. 状态栏中,行列坐标的功能没有实现。我尝试实现,但未成功。
  4. 播放背景音乐的功能。未尝试。

后续,可能会更新。。。

发布了36 篇原创文章 · 获赞 20 · 访问量 2793

猜你喜欢

转载自blog.csdn.net/weixin_43360801/article/details/104169366