數獨游戲的求解過程


前些時間在手機上下了個數獨游戲(Sudoku),用以在火車上消遣時間,游戲設置了easy,medium, hard和very hard4個難度等級。一開始玩easy的,大概6-7分鍾,后來試着來個hard,竟然花了30分鍾,太被打擊了,后來就想着來段code來節省點腦細胞。

數據游戲規則


  數獨游戲是一個9x9的網格,每個格子是1-9中的任意一個數,游戲開始時,部分格子是填好數字的,游戲內容就是將空白格子填上數字,使得

  • 每行都沒有重復數字
  • 每列都沒有重復數字
  • 每個3x3的網格內也沒有重復數字

  如下圖所示,這是一個Very Hard級別的數獨。

游戲求解


  如何來求解這個問題呢?一開始想着根據自己玩游戲的方法來處理。最直觀的一條規則:

先分析出每個位置的可能值,將那些可能取值僅有1個的格子確定下來

  這樣就會減少該格子所在行列和子塊的空格的可能取值,采用遞歸的方式就可以依次得到其他格子的數字。如下面圖中填寫的紅色數字。

  想法是美好的,可現實卻是殘酷的,搞好一運行,部分easy的可以解決,可大部分的游戲都解決不了,更不用說最前面那個Very Hard圖中的那個例子了。
  顯然,上述規則還不夠完備,導致某些問題解決不了,再仔細想想自己玩游戲的時候的思路,由於網格是9x9的,那么每行,每列和每個子塊都必然包含1-9的所有數字,下面考慮同一行中的情況(同列和同子塊類似):

若某個格子可取值123,但該行其他所有空行都不可能去1,那么該格子必須取1

  這條規則寫起來比第一個稍微復雜點,但還不算難,寫好后一測,easy的基本都ok,可hard的還是搞不定,只能繼續挖了。最后想到還有另外一條規則:

若同一行中,A格子可取12,B可取12,C可取123,那么可以確定AB必然1個是1另一個是2,那么C中的12就可以消掉了

  規則是想到了,可不好用code來表達,結果也就這么不了了之了。
  后來的某天,想到可以換個思路,電腦運算能力那么NB的,讓它去遍歷應該可以問題不大(當然要是寫個81重的循環估計是要崩潰的,不信可以試試),簡單的方式是采用深度優先的遞歸調用來實現,具體思路如下:

  1. 從上到下從左到右遍歷網格,到第一個空格子出,計算出空格子可能的取值
  2. 給空格子依次賦一個可能值后遞歸調用,若該值錯誤,則遞歸到某一步會導致一個空格子沒有可取的值

  直接上代碼:

#define SUDOKU_DIM 9
#define BLOCK_SIZE 3
#define SUDOKU_VALUE 9
bool Solve(byte *pSudoku)
{
    byte *pSudokuTmp = pSudoku;

    //======find first empty======
    int idx, r, c;
    pSudokuTmp = pSudoku;
    for(idx = 0; idx < SUDOKU_DIM * SUDOKU_DIM; idx++, pSudokuTmp++)
    {
        if(pSudoku[idx] == 0) break;
    }
    if(idx == SUDOKU_DIM * SUDOKU_DIM) return true;
    r = idx / SUDOKU_DIM;
    c = idx % SUDOKU_DIM;

    //======find prossiable value======
    bool bValuePossiable[SUDOKU_VALUE + 1];
    memset(bValuePossiable, 1, sizeof(bool) * (SUDOKU_VALUE + 1));

    //row
    pSudokuTmp = pSudoku + r * SUDOKU_DIM;
    for(int i = 0; i < SUDOKU_DIM; i++)
    {
        bValuePossiable[pSudokuTmp[i]] = false;
    }

    //col
    pSudokuTmp = pSudoku + c;
    for(int i = 0; i < SUDOKU_DIM; i++, pSudokuTmp += SUDOKU_DIM)
    {
        bValuePossiable[*pSudokuTmp] = false;
    }

    //block
    int startRow = (r / BLOCK_SIZE) * BLOCK_SIZE;
    int startCol = (c / BLOCK_SIZE) * BLOCK_SIZE;
    pSudokuTmp = pSudoku + startRow * SUDOKU_DIM + startCol;
    for(int i = 0; i < BLOCK_SIZE; i++, pSudokuTmp += SUDOKU_DIM)
    {
        for(int j = 0; j < BLOCK_SIZE; j++)
        { 
            bValuePossiable[pSudokuTmp[j]] = false;
        }
    }

    //======try to decide======
    for(int i = 1; i <= SUDOKU_VALUE; i++)
    {
        if(!bValuePossiable[i]) continue;
        pSudoku[idx] = i;
        bool bret = Solve(pSudoku);
        if(bret) return true;            
    }

    pSudoku[idx] = 0;
    return false;
}

下面是測試代碼,由於這里主要關注求解方法,測試代碼寫得有點水,輸入一個數獨游戲比較麻煩:

void print_Sudoku(byte *pu8Sudoku)
{
    printf("==============begin===============\n\n");
    for(int i = 0; i < SUDOKU_DIM; i++)
    {
        for(int j = 0; j < SUDOKU_DIM; j++, pu8Sudoku++)
        {
            printf("%d  ", *pu8Sudoku);
            if((j + 1) % BLOCK_SIZE == 0)
            {
                printf(" ");
            }
        }
        printf("\n");
        if((i+1)%BLOCK_SIZE == 0)
        {
            printf("\n");
        }
    }
    printf("============== end ===============\n");
}


void test_sudoku()
{
    byte au8Sudoku[SUDOKU_DIM * SUDOKU_DIM]=

{
//5, 0, 0, 4, 1, 0, 0, 8, 0,
//9, 0, 0, 0, 0, 0, 1, 0, 0,
//8, 0, 0, 6, 7, 9, 0, 0, 5,
//0, 4, 0, 7, 0, 0, 5, 9, 1,
//0, 8, 0, 0, 6, 0, 0, 7, 0,
//7, 9, 1, 0, 0, 3, 0, 2, 0,
//1, 0, 0, 2, 5, 6, 0, 0, 9,
//0, 0, 7, 0, 0, 0, 0, 0, 3,
//0, 5, 0, 0, 3, 7, 0, 0, 6
//
0, 0, 0, 9, 0, 0, 1, 0, 2,
4, 0, 0, 1, 0, 6, 0, 0, 0,
0, 8, 0, 0, 2, 0, 0, 0, 5,
6, 3, 0, 0, 0, 0, 5, 0, 0,
0, 0, 4, 5, 0, 7, 9, 0, 0,
0, 0, 5, 0, 0, 0, 0, 3, 1,
9, 0, 0, 0, 6, 0, 0, 1, 0,
0, 0, 0, 8, 0, 9, 0, 0, 7,
7, 0, 6, 0, 0, 2, 0, 0, 0

// 0, 0, 0, 4, 0, 0, 3, 0, 6,
// 0, 0, 0, 7, 1, 3, 0, 0, 4,
// 5, 4, 3, 9, 0, 0, 1, 0, 0,
// 3, 9, 0, 0, 0, 0, 6, 0, 0,
// 0, 7, 8, 6, 0, 5, 9, 2, 0,
// 0, 0, 6, 0, 0, 0, 0, 3, 7,
// 0, 0, 1, 0, 0, 9, 7, 8, 2,
// 7, 0, 0, 3, 6, 2, 0, 0, 0,
// 9, 0, 5, 0, 0, 1, 0, 0, 0
};

    bool bret = Solve(au8Sudoku);
    printf("%s\n", bret? "Ok":"NO");
    print_Sudoku(au8Sudoku);
}

  運行結果如下:

  速度也比我想象的快多了,畢竟是電腦哪。當然,假如數獨有多個解,上面的算法也就只能得到一個解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM