軟件工程2017第二次做業

GitHub:sudoku

解題思路描述

剛看到題目的時候,我去,好難。吃了根冰棍冷靜下來,開始細細思考。題目的要求是隨機生成N個不重複的數獨棋盤,有兩種方案:1.用數字1~9填滿第一個九宮格,而後再去填下一個九宮格,直到九個九宮格都填滿,而且不會每一行每一列不會有相同的數字。2.用數字1填第一個九宮格,而後再填第二個.......直到九個九宮格都填入了1,再把數字變成2,再一個個地去填九宮格,以此類推,直到9個數字都填入。我認爲第2種方案實現起來會更容易一些,所以決定採用第二種方案。
接下來即是肯定數據結構和方法了。若是單純採用二維數組來實現遞歸的話,我不知道該如何表示已經遍歷過的格子。所以我決定採用鏈表加上二維數組的方式來實現數獨棋盤的生成。
具體的方法以下:每一個九宮格作一個含有九個結點的鏈表,Grid[g]存儲第g個九宮格鏈表的首結點,blocks[g]用來表示第g個九宮格中空閒位置的數量(不包括已經嘗試過的結點),遞歸函數PutNum和GetRandomValue互相配合往合適位置填入數字,當一個九宮格中沒法填入數字,則向上一個宮返回false,若是能夠則繼續往下一個九宮格填數字。若是每次都是用這種遞歸方式隨機生成數獨棋盤,這樣子效率過低,因而我想到一個方法,每隨機生成一個數獨棋盤以後,能夠調換數字,這樣就又成了一個新的數獨棋盤,考慮到左上角的數字是固定的,因此這種換數字大法能夠在一個隨機數獨棋盤的基礎上生成40320種不一樣的棋盤。要實現這種換數字大法,就必須獲取每種全排列的順序,我使用permutation函數來生成全排列並將獲取到的數據填入arr2中。git

設計實現

代碼中的全局變量弄得有點多,雖然我知道這樣很差,可是不這樣弄得話又感受很不方便。代碼中共有6個函數:BuildLinkedList()是用來創建存儲座標的鏈表;GetRandomValue(short g)是用來在第g個九宮格中放置數字num,當沒有合法位置放置時,返回false;PutNum(short g)是用來遞歸調用的函數,該函數調用GetRandomValue來放置數字num;ShowSudoku()是用來將數獨棋盤輸出到文本文件中的函數;Permutation(short length)是用來產生全排列數組的函數;Clean()函數是當沒法生成數獨棋盤的時候,對一些動態變量進行清理,防止內存泄漏。github

代碼說明

這個是用來在九宮格中隨機選取空閒可用位置的函數算法

bool GetRandomValue(short g)//在第g個九宮格中隨機選取可用的位置來放入數字
{
    if (blocks[g] == 0)return false;
    int value;
    value = rand() % blocks[g];//生成隨機數
    int i;
    Node *p1, *p2;
    for (i = 0, p2 = Grid[g], p1 = p2; i < 2 * blocks[g] - 1; i++)//p2即爲可放置數字的位置座標
    {
        if (i >= value&&row_flag[p2->row] == false && column_flag[p2->column] == false)break;
        if (i == blocks[g] - 1)p2 = Grid[g], p1 = p2;
        else
        {
            p1 = p2;
            p2 = p2->next;
        }
    }
    if (i == 2 * blocks[g] - 1)return false;
    sudoku[p2->row][p2->column] = num;//接下來的代碼是對被選中的目標位置結點進行刪除前的準備操做
    numlocation[g] = 3 * (p2->row % 3) + (p2->column % 3);
    row_flag[p2->row] = true;
    column_flag[p2->column] = true;
    if (p2 == Grid[g])Grid[g] = p2->next;
    if (p2 == LinkedListTail[g])LinkedListTail[g] = p1, p1->next = NULL;
    if (p2 != Grid[g] && p2 != LinkedListTail[g])p1->next = p2->next;
    delete p2;
    blocks[g] = blocks[g] - 1;
    return true;
}

調用GetRandomValue函數在每一個宮內放置數字的函數PutNum數組

bool PutNum(short g)//在每一個九宮格中放入相應的數字num
{
    for (;;)
    {
        if (GetRandomValue(g) == true)
        {
            if (g == 8)return true;
            else if (PutNum(g + 1) == false)//若是PutNum(g+1)返回false,則說明第g+1個宮沒法放置數字,則在第g個宮嘗試能夠放置的其餘位置,已經嘗試過的位置結點則放置到鏈表後面
            {
                Node *p = new Node;
                p->row = 3 * (g / 3) + numlocation[g] / 3;
                p->column = 3 * (g % 3) + numlocation[g] % 3;
                p->next = NULL;
                LinkedListTail[g]->next = p;
                LinkedListTail[g] = p;
                sudoku[p->row][p->column] = 0;
                row_flag[p->row] = false;
                column_flag[p->column] = false;
                continue;
            }
            else
            {
                blocks[g] = 9 - num;
                return true;
            }
        }
        else
        {
            blocks[g] = 10 - num;
            return false;
        }

    }
}

生成全排列並存放在arr2數組中的函數Permutation數據結構

void Permutation(short length)//用遞歸的方法在arr2數組中生成全排列
{
    int i;
    if (length == 10 - sudoku[0][0])
    {
        if (length != 1)Permutation(length - 1);
        else
            ShowSudoku();
        return;
    }
    for (i = 0; i<9 && stop_flag; i++)
    {
        if (arr1[i] == 0)
        {
            arr1[i] = 1;
            arr2[9 - length] = i + 1;
            if (length != 1)Permutation(length - 1);
            else
                ShowSudoku();
            arr1[i] = 0;
        }
    }
}

將數獨棋盤輸出到文本文件中dom

void ShowSudoku()//將數獨棋盤輸出到文本文件中
{

    int row, column;
    for (row = 0; row < 9; row++)
    {
        for (column = 0; column < 8; column++)
        {
            fcout << arr2[sudoku[row][column] - 1] << " ";//將隨機生成的數獨棋盤映射到arr2數組中
        }
        fcout << arr2[sudoku[row][column] - 1] << endl;
    }
    if (--sudoku_count == 0)stop_flag = false;
    else
        fcout << endl;
}

測試運行

測試運行的截圖

函數

效能分析與改進

分析時生成的數獨棋盤個數設爲50000個


學習

使用鏈表來進行生成數獨棋盤真的很費時,效率不高,並且又佔用空間。若是時間容許的話,我想不用鏈表來作,可是不用鏈表如何表示已經遍歷過的位置對我來講是個問題,寫完這篇博客準備去研究一下。測試

PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 720 1200
· Estimate · 估計這個任務須要多少時間 720 1200
Development 開發 660 1020
· Analysis · 需求分析 (包括學習新技術) 120 120
· Design Spec · 生成設計文檔 0 0
· Design Review · 設計複審 (和同事審覈設計文檔) 0 0
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 30 30
· Design · 具體設計 60 180
· Coding · 具體編碼 360 600
· Code Review · 代碼複審 30 30
· Test · 測試(自我測試,修改代碼,提交修改) 60 60
Reporting 報告 60 180
· Test Report · 測試報告 0 0
· Size Measurement · 計算工做量 30 60
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 30 120
合計 720 1200

我的總結

第二次做業對我我的來說難度仍是很大的,此次代碼用的數據結構也不是很好,處理鏈表帶來的時間和空間開銷都比較大。生成一個數獨棋盤後替換數字又生成了另一個棋盤是一個比較取巧的辦法,時間開銷比遞歸生成數獨棋盤所用的時間要小,所以我採用了遞歸生成數獨和換數字相結合的方式。還有就是這個完成這個做業的耗時遠遠在個人意料以外,花的時間實在是太長了,主要是前期規劃不怎麼好,致使編碼的時候bug一大堆,很容易就中止運行了,也算是吸收一個教訓了。最後一個就是本身的算法功底太薄弱了,代碼也寫的比較臃腫,新的學期要好好學習算法和代碼的優化方法。優化

更新

得老師指點,將編譯模式改成RELEASE模式,時間損耗是原來的四分之一,速度獲得極大提高,所以對「效能分析與改進」板塊進行修改,用RELEASE版的截圖覆蓋了原來的DEBUG版截圖,並將RELEASE版的程序更新到GitHub上。 突然發現若是將srand(time(0))放到遞歸函數中產生的隨機數在短期內會相等,而採用clock()函數作種子又會形成運行兩次生成的2個文本文件有必定機率相等,而後嘗試把srand(time(0))放到主函數中只調用一次,rand()函數放到遞歸函數中調用則產生的隨機數即便在短期內也不會相等,所以修改了隨機數產生的代碼,並將新的cpp和exe文件上傳到GitHub上。

相關文章
相關標籤/搜索