八葉一刀流·二之型·疾風數獨

項目相關要求

Github 項目地址:https://github.com/zjwml4581792/Software-engineeringhtml


第一次的真軟工實踐做業就是這種題目,想一想還有點小激動,終於又要敲代碼啦。看着本身如今的編程的「紙飛機階段」,想完成,仍是等待~並心懷但願吧!git


先看題目。github

我是題目算法

度娘說:數獨(百度百科)編程

數獨(Sudoku,Crosswords)是源自18世紀瑞士的一種數學遊戲。是一種運用紙、筆進行演算的邏輯遊戲。玩家須要根據9×9盤面上的已知數字,推理出全部剩餘空格的數字,並知足每一行、每一列、每個粗線宮(3*3)內的數字均含1-9,不重複。
數獨盤面是個九宮,每一宮又分爲九個小格。在這八十一格中給出必定的已知數字和解題條件,利用邏輯和推理,在其餘的空格上填入1-9的數字。使1-9每一個數字在每一行、每一列和每一宮中都只出現一次,因此又稱「九宮格」。數組

image


工具清單

  • 編程語言: C/C++
  • 編程IDE:Visual Studio 2017 社區版
  • 效能分析工具:Visual Studio Profiling Tools
  • 源代碼管理平臺:Github
  • markdown 編輯器:有道雲筆記

解題思路

遇到的困難及解決方法

  • 困難描述

看到的第一眼就是,這個題目跟程序語言綜合設計的那個馬踏棋盤和N皇后問題好像啊,一百度,果真三個題目常常綁定出現,用的不少都是回溯。markdown

然而本身並非很懂回溯,因而本身找了找不是回溯算法的大神寫的博客。惋惜大神寫的算法我基本看不懂。dom

各類各樣的問題顯示了本身的功力不足,函數何時開始回溯啊,調用函數本身的那行到底寫在哪裏啊,比較好的想法究竟是哪種啊……編程語言

  • 作過哪些嘗試

本身什麼都不懂,直接開始寫是不現實的,因而開始問學長。編輯器

先問了個直系老鄉研究生谷歌(仍是微軟)大佬,說「數獨問題用 dancing links 算法來寫」,,一百度好像dancing links 真的很厲害的樣子,可是好像這麼複雜的雙向鏈表我真的寫不出來。

本身百度「數獨生成算法」吧,看看前輩們都用的是什麼算法,用的思想是否是太超前本身能不能接受。

找到的比較看得懂本身印象比較深有啓發的:

  1. 【算法研究】數獨高效徹底解生成算法的研究和實現
  2. JavaScript九宮格數獨生成算法
  3. 五大經常使用算法之四:回溯法

因而本身嘗試了生成 9 個隨機數組一行一行填進矩陣,一個一個數字地填,一個九宮填一個九宮地填等等各類方法。

  • 是否解決

最後採用了 0~9 輪流逐行地填的方法,即從1開始填,從第0行填到第8行,1填完了填2,直到9。

事實證實採用了某種方法若是理論沒問題必定要堅持到底,若是不斷地嘗試別的方法真的沒有效率,就跟《構建之法》裏的畫扇面同樣,過早想着優化,都還沒完成必要功能和需求就不要考慮太多。先寫出來了再說。

  • 有何收穫

【1】 中汲取了「按九宮格從左到右從上到下,從 1-9 輪流填,填完了再填下一個數」的思想並改造;

【2】 中獲得了先把無依賴的主對角線上的三個小九宮填滿,再填其餘九宮的思想(可是最終並無用到這方法

【3】 中瞭解了怎麼寫回溯算法。

本身的搜索能力暴漲,code能力小幅升,熬夜能力MAX,泡麪技巧infinite
拖延症已病入膏肓

關鍵代碼

習慣看到題目就先建立個類,這個數獨類至少須要有構造函數、析構函數、建立矩陣、打印矩陣四個函數,數獨矩陣二維數組,而後根據須要添加了初始化函數,判斷函數,文件變量等,詳見代碼註釋。

class Sudo
{
public:
    Sudo(int id);               //構造函數
    ~Sudo();                    //析構函數
    void createSudoku(int id);//建立數獨矩陣
    void print();               //打印
    void init();                //初始化

private:
    ofstream outfile;               //輸出文件
    int arr[9][9];              //數獨矩陣數組
    int flag;               //是否把每一行的該數都填好的標誌
    int firstNumber;                    //題目要求的跟學號有關的數
    bool isRight(int row, int column, int num);//判斷row行column列是否能夠填num
    vector<int> randVector();       //生成隨機數組
    void fillNumber(int num, int row);  //從row開始每一行開始填充num
    void clean(int i);                  //填充失敗,把i數清除,從新填過
};

看到題目想到要寫的第一個函數就是判斷函數,在寫的時候先分別寫了行判斷、列判斷、九宮判斷三個函數,以及調用三個函數的isRight原始版,後來整合到一塊兒了就成了下面的新生版。因爲是逐行填充的,只須要檢查列和九宮符不符合要求便可。

後來思考若是按照每一個九宮來填數,只須要判斷行和列會不會更快,可是按九宮來填數,填數就麻煩了,根據第一個博客裏的來看,應該按九宮來會快些。

bool Sudo::isRight(int row, int column, int num)
{//判斷所在位置是否可行
    for (int i = 0; i < 9; i++)
    {//檢查列可不能夠 
        if (arr[i][column] == num)
            return false;
    }
    int northwestX = row / 3 * 3;//算小九宮的第一個位置
    int northwestY = column / 3 * 3;
    //檢查九宮 
    for (int i = 0; i < 3; i++)//橫向檢查
    {                           
        for (int j = 0; j < 3; j++)//豎向檢查
        {                       
            if (arr[i + northwestX][j + northwestY] == num)
                return false;
        }
    }
    return true;
}

若是是普通的隨機數的生成的話,必定會生成重複的隨機數,因而生成一個0-8的隨機數組,須要用的時候遍歷數組。
如何生成隨機數組的方法是網上找的,C++下數組隨機shuffle的方法解

vector<int> Sudo::randVector()//生成隨機數組
{
    vector<int> result;
    result.clear();
    for (int i = 0; i < 9; i++)
        result.push_back(i);

    random_shuffle(result.begin(), result.end());
    return result;
}

建立數獨,先填要求的數,再從1-9填,無法填了就退回上一個數從新填過。

void Sudo::createSudoku(int hhhhhh)//建立數獨
{
    fillNumber(firstNumber, 1); //先把要求的那個數填進去
    for (int i = 1; i < 9; i++) //從1到9開始填充
    {
        if (i == firstNumber)//若是是要求的那個數
            continue;   //則跳過

        fillNumber(i, 0);   //逐行填充數i

        if (flag)       //若是填充好了
            flag = false;   //將flag置false,準備下一個數
        else        //若是沒填好
        {
            clean(i);//把上一個數清掉,重填
            i -= 2;     //先-2,待會+1,從上一個數從新開始
        }
    }

    for (int i = 0; i < 9; i++)//發現上面填完了以後9是空的
    {
        for (int j = 0; j < 9; j++)//就寫一個循環把9補上
        {
            if (arr[i][j] == 0)
                arr[i][j] = 9;
        }
    }
}

最重要的填數,不過這個回溯挺最簡單,從第0行開始填num。

void Sudo::fillNumber(int num, int row)//從row行開始填充num
{
    if (row > 8)        //若是0-8行都填完了
    {
        flag = true;    //將填好的標誌flag置true
        return;     //返回
    }

    vector<int>temp = randVector(); //生成隨機數組,
    for (int i = 0; i < 9; i++) //從數組第一個數開始測試第row行temp[i]列行不行
    {
        if (arr[row][temp.at(i)] != 0)//若是這個位置填過了
            continue;   //跳過這列

        if (isRight(row, temp.at(i), num))//若是這個位置能夠
        {
            arr[row][temp.at(i)] = num; //填上去
            fillNumber(num, row + 1);   //填下一個行
            if (flag)           //若是填好了
                return;         //返回
            arr[row][temp.at(i)] = 0;//若是這個數這行填不成,把上一行該數置0,換temp下一個數試試
        }
    }
}

程序運行截圖

圖中的運行時間是測試用的,暴露了本身這個算法有待改進,很大的改進。

運行截圖

效能分析

摘要

摘要

調用關係樹

調用關係樹

從中能夠看出,主要是填充數函數佔了最多時間,所以算法自己有很大的改進空間,好比判斷函數,從周同窗的做業中,能夠看出本身和TA的差距;也能夠把fillNumber函數的返回值設爲Bool,也能少個變量flag。另外vector系列也是比較耗時的,用的很少的話,本身寫個隨機生成數組的函數,藉助庫函數是比較耗時。

關於執行力、泛泛而談的理解

百毒百科說

執行力是指有效利用資源、保質保量達成目標的能力,指的是貫徹戰略意圖,完成預約目標的操做能力。是把企業戰略、規劃、目標轉化成爲效益、成果的關鍵。
執行力包含完成任務的意願,完成任務的能力,完成任務的程度。對我的而言執行力就是辦事能力;對團隊而言執行力就是戰鬥力;對企業而言執行力就是經營能力。
簡單來講就是行動力。

個人理解就是,在拿到任務以後,用本身最專一的熱情去完成這件事,最及時地完成,對待任務當機立斷、雷厲風行,以秋風掃落葉之勢,腳踏實地、貫徹始終,知行合一。

其實上面的這段話就是泛泛而談,沒有什麼針對性,就好比創新創業比賽上,上臺的不少學生都是侃侃而談,對於本身的產品自己的介紹並很少,就算是客觀的場面話,也沒有講到針對於自身產品最大的痛點。


PSP

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 20 10
· Estimate · 估計這個任務須要多少時間 20 10
Development 開發 1775 1820
· Analysis · 需求分析 (包括學習新技術) 60 73
· Design Spec · 生成設計文檔 30 沒有作
· Design Review · 設計複審 (和同事審覈設計文檔) 30 單幹,不須要
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 15 7
· Design · 具體設計 120 120
· Coding · 具體編碼 1440 1560
· Code Review · 代碼複審 60 20
· Test · 測試(自我測試,修改代碼,提交修改) 20 40
Reporting 報告 155 78
· Test Report · 測試報告 30 30
· Size Measurement · 計算工做量 15 15
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 120 33
合計 1960 1908

學習進度條

第 N 周 新增代碼(行) 累計代碼(行) 學習耗時(小時) 累計學習耗時(小時) 重要成長
第 0 周 192 192 31 31 複習C++語法、學習VS2017操做、瞭解回溯

參考資料

相關文章
相關標籤/搜索