N个投机取巧的数独棋盘

1,代码:

1.1主函数 

#include "stdafx.h"

#include <stdlib.h>

#include "iostream"

#include "ChessBoard.h"

#include "fstream"

#include "sstream"

using namespace std;

 

int main(int argc, char* argv[])

{

    ChessBoard CB;

    int bn = 0;//board number

    string s = argv[2];

    stringstream ss;

    ss << s;

    ss >> bn;

    ofstream file("sudotiku.txt");

    for (int i = 0; i < bn; i++){

        CB.printChessBoard(file);

        CB.exchange();  

        file << endl;

        cout << endl;

    }

    file.close();

    cout << "执行完毕,请检查";

    system("pause");

    return 0;

}

1.2类头文件

#pragma once

#include "fstream"

using namespace std;

class ChessBoard

{

public:

    ChessBoard();

    ~ChessBoard();

    int*  getExchangedNum();

    ofstream& printChessBoard(ofstream &);

    void exchange();

private:

    int exchangedNum[9];//已经交换过的数

    int enumNum[9];

    int chessBoardRelationship[9][9];

};

1.3类

#include "stdafx.h"

#include "ChessBoard.h"

#include "iostream"

#include <stdlib.h>

#include<windows.h>

#include "fstream"

using namespace std;

ChessBoard::ChessBoard()

{

    int init[9][9] ={

            { 1, 2, 3, 5, 6, 7, 9, 4, 8 },

            { 4, 5, 6, 8, 9, 1, 3, 7, 2 },

            { 7, 8, 9, 2, 3, 4, 6, 1, 5 },

            { 9, 4, 8, 1, 2, 3, 5, 6, 7 },

            { 3, 7, 2, 4, 5, 6, 8, 9, 1 },

            { 6, 1, 5, 7, 8, 9, 2, 3, 4 },

            { 5, 6, 7, 9, 4, 8, 1, 2, 3 },

            { 8, 9, 1, 3, 7, 2, 4, 5, 6 },

            { 2, 3, 4, 6, 1, 5, 7, 8, 9 }

    };

    for (int i = 0; i < 9; i++){

        for (int j = 0; j < 9; j++){

            chessBoardRelationship[i][j]=init[i][j];  

        }

    }

    exchange();

}

void ChessBoard::exchange(){

    for (int i = 0; i < 9; i++){

        enumNum[i] = i + 1;

    }

    int p = 0;

    for (int i = 0; i < 9; i++){

 

        LARGE_INTEGER nFrequency;

        if (::QueryPerformanceFrequency(&nFrequency))

        {

            LARGE_INTEGER nStartCounter;

            ::QueryPerformanceCounter(&nStartCounter);

            ::srand((unsigned)nStartCounter.LowPart);

        }

 

        p = rand() % (9 - i);

        exchangedNum[i] = enumNum[p];

        enumNum[p] = enumNum[8 - i];

    }

}

ChessBoard::~ChessBoard()

{

}

int* ChessBoard::getExchangedNum(){

    return &(exchangedNum[0]);

}

ofstream & ChessBoard::printChessBoard(ofstream &file){

    int count=0;

    int count1=0;

    for (int i = 0; i < 9; i++){

        for (int j = 0; j < 9; j++){

            cout << exchangedNum[chessBoardRelationship[i][j]-1]<<" ";

            file << exchangedNum[chessBoardRelationship[i][j] - 1] << " ";

            count++;

            if (count % 3 == 0){

                cout << "   ";

                file << "   ";

            }

        }

        count1++;

        if (count1 % 3 == 0){

            cout << endl;

            file << endl;

        }

        cout << endl;

        file << endl;

    }

    return file;

}

2,分析:

  以上代码几乎没有注释,这没有关系,本人也从来没有玩过数独游戏,这也没关系,让我们从最原始的问题开始。

  首先,一个数独棋盘应是有解的,在提示数足够多的情况下,它应是有唯一解的。我曾注意到一位同僚,在随机生成n个数独棋盘这个问题上,采取如下策略:先求解出一个棋盘,然后,随机重新排列每个三行、每个三列、每三个三行和每三个三列。这样做显然不会违背数独规则,但是,其中似乎有些不变的东西。

  从线性代数的角度看,这显然是一个无法再化简的矩阵,也就是说,无论怎样变化,它还是同一个矩阵,那么,我们可以很容易地手写一个矩阵,假定它最终可以变换为这样的关系形式:

 1, 2, 3,  5, 6, 7,  9, 4, 8 
    4, 5, 6,  8, 9, 1,  3, 7, 2 
    7, 8, 9,  2, 3, 4,  6, 1, 5 
    9, 4, 8,  1, 2, 3,  5, 6, 7 
    3, 7, 2,  4, 5, 6,  8, 9, 1 
    6, 1, 5,  7, 8, 9,  2, 3, 4 
    5, 6, 7,  9, 4, 8,  1, 2, 3 
    8, 9, 1,  3, 7, 2,  4, 5, 6 
    2, 3, 4,  6, 1, 5,  7, 8, 9 

  请注意,这只是一种关系形式,而不是最终输出的数值,其中每两个数之间没有任何关系,这个矩阵嘛就是代码中给出的chessBoardRelationship矩阵。

  这个矩阵明显是死的,那么我们应当对它进行变换,按照上面的描述,策略如下:1对应的数值是在1到9之中随机的,而且一旦确定,则这个对应的映射关系不能被其它映射所破坏,即数值不能被重复选定。我们使用两个数组来描述这样的映射关系。

  最终问题,输出,按照关系数组的格式,根据关系数组的数值查找映射,输出数值,由于此时关系数组每个数值对应的映射时随机的,可能出现的映射总量为9位数,问题得到解决。

  这其中涉及到一些细节,比如,如何让映射关系不重复,如何在运行函数时传入参数,内联函数等,这些在网络上都能得到详尽的解答,再次不再赘述。

  关于程序运行的效率,需要在此代码的基础上屏蔽到控制台的输出,然后测试,经测试,实际在形成10000个数独棋盘时,命令行下光标闪烁5次,由于在本机上光标每秒闪烁一次,估算的运行效率为,5秒以内,形成10000个已经解答的数独棋盘并输出到文件。经使用visual studio自带测试工具,总cpu时间稳定在4700毫秒。(测试平台:ThinkPad E485 AMD_Ryzen5 2500U 8G)

3,心得

  3.1,写程序要善于“投机取巧”,

  3.2,要善于思考已有的方案。

  最开始拿到这个题目时,我曾花费大量精力,连续四天,力图使其在每一个位置上随机输出正确的数字,并力图一次形成未解答的棋盘和答案,甚至离成功只有一步之遥,然而,在了解他人的做法以后,迅速跳出了这个圈子,即便是想达到题目之外的要求,也可以先得出已解答的棋盘,再从中选取提示数,从构思直到最终确认实现题目要求,在几乎没有c++基础的情况下,6小时就达成要求。

posted on 2018-10-04 17:18  沸腾的土豆泥  阅读(356)  评论(4编辑  收藏  举报