在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

搞懂回溯算法,做數(shù)獨(dú)都變簡(jiǎn)單了

jf_78858299 ? 來(lái)源:labuladong ? 作者:labuladong ? 2023-04-19 11:02 ? 次閱讀

經(jīng)常拿回溯算法來(lái)說(shuō)事兒的,無(wú)非就是八皇后問(wèn)題和數(shù)獨(dú)問(wèn)題了。那我們今天就通過(guò)實(shí)際且有趣的例子來(lái)講一下如何用回溯算法來(lái)解決數(shù)獨(dú)問(wèn)題。

一、直觀感受

說(shuō)實(shí)話我小的時(shí)候也嘗試過(guò)玩數(shù)獨(dú)游戲,但從來(lái)都沒(méi)有完成過(guò)一次。做數(shù)獨(dú)是有技巧的,我記得一些比較專(zhuān)業(yè)的數(shù)獨(dú)游戲軟件,他們會(huì)教你玩數(shù)獨(dú)的技巧,不過(guò)在我看來(lái)這些技巧都太復(fù)雜,我根本就沒(méi)有興趣看下去。

不過(guò)自從我學(xué)習(xí)了算法,多困難的數(shù)獨(dú)問(wèn)題都攔不住我了。下面是我用程序完成數(shù)獨(dú)的一個(gè)例子:

圖片

PS:GIF 可能出現(xiàn) bug,若卡住點(diǎn)開(kāi)查看即可,下同。

這是一個(gè)安卓手機(jī)中的數(shù)獨(dú)游戲,我使用一個(gè)叫做 Auto.js 的腳本引擎,配合回溯算法來(lái)實(shí)現(xiàn)自動(dòng)完成填寫(xiě),并且算法記錄了執(zhí)行次數(shù)。

在后文,我會(huì)給出該腳本的實(shí)現(xiàn)思路代碼以及軟件工具的下載,你也可以拿來(lái)裝逼用 。

可以觀察到前兩次都執(zhí)行了 1 萬(wàn)多次,而最后一次只執(zhí)行了 100 多次就算出了答案,這說(shuō)明對(duì)于不同的局面,回溯算法得到答案的時(shí)間是不相同的。

那么計(jì)算機(jī)如何解決數(shù)獨(dú)問(wèn)題呢?其實(shí)非常的簡(jiǎn)單,就是窮舉嘛,下面我可視化了求解過(guò)程:

圖片

算法的核心思路非常非常的簡(jiǎn)單,就是對(duì)每一個(gè)空著的格子窮舉 1 到 9,如果遇到不合法的數(shù)字(在同一行或同一列或同一個(gè) 3×3 的區(qū)域中存在相同的數(shù)字)則跳過(guò),如果找到一個(gè)合法的數(shù)字,則繼續(xù)窮舉下一個(gè)空格子

對(duì)于數(shù)獨(dú)游戲,也許我們還會(huì)有另一個(gè)誤區(qū):就是下意識(shí)地認(rèn)為如果給定的數(shù)字越少那么這個(gè)局面的難度就越大。

這個(gè)結(jié)論對(duì)人來(lái)說(shuō)應(yīng)該沒(méi)毛病,但對(duì)于計(jì)算機(jī)而言,給的數(shù)字越少,反而窮舉的步數(shù)就越少,得到答案的速度越快,至于為什么,我們后面探討代碼實(shí)現(xiàn)的時(shí)候會(huì)講。

上一個(gè) GIF 是最后一關(guān) 70 關(guān),下圖是第 52 關(guān),數(shù)字比較多,看起來(lái)似乎不難,但是我們看一下算法執(zhí)行的過(guò)程:

圖片

可以看到算法在前兩行窮舉了半天都沒(méi)有走出去,由于時(shí)間原因我就沒(méi)有繼續(xù)錄制了,事實(shí)上,這個(gè)局面窮舉的次數(shù)大概是上一個(gè)局面的 10 倍。

言歸正傳,下面我們就來(lái)具體探討一下如何用算法來(lái)求解數(shù)獨(dú)問(wèn)題,順便說(shuō)說(shuō)我是如何可視化這個(gè)求解過(guò)程的 。

二、代碼實(shí)現(xiàn)

首先,我們不用管游戲的 UI,先單純地解決回溯算法,LeetCode 第 37 題就是解數(shù)獨(dú)的問(wèn)題,算法函數(shù)簽名如下:

void solveSudoku(char[][] board);

輸入是一個(gè)9x9的棋盤(pán),空白格子用點(diǎn)號(hào)字符.表示,算法需要在原地修改棋盤(pán),將空白格子填上數(shù)字,得到一個(gè)可行解。

至于數(shù)獨(dú)的要求,大家想必都很熟悉了,每行,每列以及每一個(gè) 3×3 的小方格都不能有相同的數(shù)字出現(xiàn)。那么,現(xiàn)在我們直接套回溯框架即可求解。

前文 [回溯算法詳解],已經(jīng)寫(xiě)過(guò)了回溯算法的套路框架,如果還沒(méi)看過(guò)那篇文章的,建議先看看 。

回憶剛才的 GIF 圖片,我們求解數(shù)獨(dú)的思路很簡(jiǎn)單粗暴,就是對(duì)每一個(gè)格子所有可能的數(shù)字進(jìn)行窮舉。對(duì)于每個(gè)位置,應(yīng)該如何窮舉,有幾個(gè)選擇呢?

很簡(jiǎn)單啊,從 1 到 9 就是選擇,全部試一遍不就行了

// 對(duì) board[i][j] 進(jìn)行窮舉嘗試
void backtrack(char[][] board, int i, int j) {
    int m = 9, n = 9;
    for (char ch = '1'; ch <= '9'; ch++) {
        // 做選擇
        board[i][j] = ch;
        // 繼續(xù)窮舉下一個(gè)
        backtrack(board, i, j + 1);
        // 撤銷(xiāo)選擇
        board[i][j] = '.';
    }
}

emmm,再繼續(xù)細(xì)化,并不是 1 到 9 都可以取到的,有的數(shù)字不是不滿(mǎn)足數(shù)獨(dú)的合法條件嗎?而且現(xiàn)在只是給j加一,那如果j加到最后一列了,怎么辦?

很簡(jiǎn)單,當(dāng)j到達(dá)超過(guò)每一行的最后一個(gè)索引時(shí),轉(zhuǎn)為增加i開(kāi)始窮舉下一行,并且在窮舉之前添加一個(gè)判斷,跳過(guò)不滿(mǎn)足條件的數(shù)字

void backtrack(char[][] board, int i, int j) {
    int m = 9, n = 9;
    if (j == n) {
        // 窮舉到最后一列的話就換到下一行重新開(kāi)始。
        backtrack(board, i + 1, 0);
        return;
    }

    // 如果該位置是預(yù)設(shè)的數(shù)字,不用我們操心
    if (board[i][j] != '.') {
        backtrack(board, i, j + 1);
        return;
    } 

    for (char ch = '1'; ch <= '9'; ch++) {
        // 如果遇到不合法的數(shù)字,就跳過(guò)
        if (!isValid(board, i, j, ch))
            continue;

        board[i][j] = ch;
        backtrack(board, i, j + 1);
        board[i][j] = '.';
    }
}

// 判斷 board[r][c] 是否可以填入 n
boolean isValid(char[][] board, int r, int c, char n) {
    for (int i = 0; i < 9; i++) {
        // 判斷行是否存在重復(fù)
        if (board[r][i] == n) return false;
        // 判斷列是否存在重復(fù)
        if (board[i][c] == n) return false;
        // 判斷 3 x 3 方框是否存在重復(fù)
        if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n)
            return false;
    }
    return true;
}

emmm,現(xiàn)在基本上差不多了,還剩最后一個(gè)問(wèn)題:這個(gè)算法沒(méi)有 base case,永遠(yuǎn)不會(huì)停止遞歸。這個(gè)好辦,什么時(shí)候結(jié)束遞歸? 顯然r == m的時(shí)候就說(shuō)明窮舉完了最后一行,完成了所有的窮舉,就是 base case

另外,前文也提到過(guò),為了減少?gòu)?fù)雜度,我們可以讓backtrack函數(shù)返回值為boolean,如果找到一個(gè)可行解就返回 true,這樣就可以阻止后續(xù)的遞歸。只找一個(gè)可行解,也是題目的本意。

最終代碼修改如下:

boolean backtrack(char[][] board, int i, int j) {
    int m = 9, n = 9;
    if (j == n) {
        // 窮舉到最后一列的話就換到下一行重新開(kāi)始。
        return backtrack(board, i + 1, 0);
    }
    if (i == m) {
        // 找到一個(gè)可行解,觸發(fā) base case
        return true;
    }

    if (board[i][j] != '.') {
        // 如果有預(yù)設(shè)數(shù)字,不用我們窮舉
        return backtrack(board, i, j + 1);
    } 

    for (char ch = '1'; ch <= '9'; ch++) {
        // 如果遇到不合法的數(shù)字,就跳過(guò)
        if (!isValid(board, i, j, ch))
            continue;

        board[i][j] = ch;
        // 如果找到一個(gè)可行解,立即結(jié)束
        if (backtrack(board, i, j + 1)) {
            return true;
        }
        board[i][j] = '.';
    }
    // 窮舉完 1~9,依然沒(méi)有找到可行解,此路不通
    return false;
}

boolean isValid(char[][] board, int r, int c, char n) {
    // 見(jiàn)上文
}

現(xiàn)在可以回答一下之前的問(wèn)題,為什么有時(shí)候算法執(zhí)行的次數(shù)多,有時(shí)候少?為什么對(duì)于計(jì)算機(jī)而言,確定的數(shù)字越少,反而算出答案的速度越快

我們已經(jīng)實(shí)現(xiàn)了一遍算法,掌握了其原理,回溯就是從 1 開(kāi)始對(duì)每個(gè)格子窮舉,最后只要試出一個(gè)可行解,就會(huì)立即停止后續(xù)的遞歸窮舉。所以暴力試出答案的次數(shù)和隨機(jī)生成的棋盤(pán)關(guān)系很大,這個(gè)是說(shuō)不準(zhǔn)的。

那么你可能問(wèn), 既然運(yùn)行次數(shù)說(shuō)不準(zhǔn),那么這個(gè)算法的時(shí)間復(fù)雜度是多少呢 ?

對(duì)于這種時(shí)間復(fù)雜度的計(jì)算,我們只能給出一個(gè)最壞情況,也就是 O(9^M),其中M是棋盤(pán)中空著的格子數(shù)量。你想嘛,對(duì)每個(gè)空格子窮舉 9 個(gè)數(shù),結(jié)果就是指數(shù)級(jí)的。

這個(gè)復(fù)雜度非常高,但稍作思考就能發(fā)現(xiàn),實(shí)際上我們并沒(méi)有真的對(duì)每個(gè)空格都窮舉 9 次,有的數(shù)字會(huì)跳過(guò),有的數(shù)字根本就沒(méi)有窮舉,因?yàn)楫?dāng)我們找到一個(gè)可行解的時(shí)候就立即結(jié)束了,后續(xù)的遞歸都沒(méi)有展開(kāi)。

這個(gè) O(9^M) 的復(fù)雜度實(shí)際上是完全窮舉,或者說(shuō)是找到所有可行解的時(shí)間復(fù)雜度。

如果給定的數(shù)字越少,相當(dāng)于給出的約束條件越少,對(duì)于計(jì)算機(jī)這種窮舉策略來(lái)說(shuō),是更容易進(jìn)行下去,而不容易走回頭路進(jìn)行回溯的,所以說(shuō) 如果僅僅找出一個(gè)可行解 ,這種情況下窮舉的速度反而比較快。

至此,回溯算法就完成了,你可以用以上代碼通過(guò) LeetCode 的判題系統(tǒng),下面我們來(lái)簡(jiǎn)單說(shuō)下我是如何把這個(gè)回溯過(guò)程可視化出來(lái)的。

三、算法可視化

讓算法幫我玩游戲的核心是算法,如果你理解了這個(gè)算法,剩下就是借助安卓腳本引擎 Auto.js 調(diào) API 操作手機(jī)了,工具我都放在后臺(tái)了,你等會(huì)兒就可以下載。

用偽碼簡(jiǎn)單說(shuō)下思路,我可以寫(xiě)兩個(gè)函數(shù):

void setNum(Button b, char n) {
    // 輸入一個(gè)方格,將該方格設(shè)置為數(shù)字 n
}

void cancelNum(Button b) {
    // 輸入一個(gè)方格,將該方格上的數(shù)字撤銷(xiāo)
}

回溯算法的核心框架如下, 只要在框架對(duì)應(yīng)的位置加上對(duì)應(yīng)的操作,即可將算法做選擇、撤銷(xiāo)選擇的過(guò)程完全展示出來(lái) ,也許這就是套路框架的魅力所在:

for (char ch = '1'; ch <= '9'; ch++) {
    Button b = new Button(r, c);
    // 做選擇
    setNum(b, ch);
    board[i][j] = ch;
    // 繼續(xù)窮舉下一個(gè)
    backtrack(board, i, j + 1);
    // 撤銷(xiāo)選擇
    cancelNum(b);
    board[i][j] = '.';
}
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    改進(jìn)的數(shù)LMS自適應(yīng)濾波算法

    為了減小分?jǐn)?shù)階數(shù)數(shù)最小均方算法(FTLMS)穩(wěn)態(tài)濾波器階數(shù)誤差,提出了一種誤差寬度的
    發(fā)表于 05-13 09:05

    回溯經(jīng)典 (五皇后問(wèn)題) (算法)

    5皇后問(wèn)題:在8*8的國(guó)際象棋棋盤(pán)上,放5個(gè)皇后,使它們控制整個(gè)棋盤(pán),即在任何一格放一個(gè)棋子,都會(huì)馬上被吃掉。下面介紹回溯解法定義一個(gè)表示點(diǎn)的數(shù)據(jù)結(jié)構(gòu): struct Pt {Int x,y
    發(fā)表于 08-16 14:56

    數(shù)獨(dú)/九宮格 求解

    本帖最后由 shi_dongyu 于 2017-3-8 20:57 編輯 說(shuō)明:本程序采用簡(jiǎn)單的群舉算法來(lái)解決數(shù)獨(dú)游戲,且能找到所有的數(shù)
    發(fā)表于 03-08 20:16

    proteus的漢字怎么方框?請(qǐng)教了

    去年的圖標(biāo)注漢字,今天打開(kāi)漢字沒(méi)了,方框?請(qǐng)教什么原因?
    發(fā)表于 05-05 05:54

    基于回溯的RFID防沖撞算法

    針對(duì)RFID 系統(tǒng)中常見(jiàn)的沖撞問(wèn)題,提出一種基于回溯的精簡(jiǎn)結(jié)點(diǎn)二叉樹(shù)搜索防沖撞算法,在分析二進(jìn)制搜索和動(dòng)態(tài)二進(jìn)制算法性能的基礎(chǔ)上,得出了提高效率的關(guān)鍵所在,在達(dá)到
    發(fā)表于 12-18 12:06 ?18次下載

    模板方法模式在回溯算法中的應(yīng)用

    描述模板方法模式及回溯算法的模板方法模式的Java 語(yǔ)言實(shí)現(xiàn),該實(shí)現(xiàn)使得回溯算法的實(shí)現(xiàn)達(dá)到了可擴(kuò)展性、靈活性和可插入性三個(gè)目標(biāo),提高了
    發(fā)表于 01-15 16:48 ?20次下載

    模板方法模式在回溯算法中的應(yīng)用

    描述模板方法模式及回溯算法的模板方法模式的Java 語(yǔ)言實(shí)現(xiàn),該實(shí)現(xiàn)使得回溯算法的實(shí)現(xiàn)達(dá)到了可擴(kuò)展性、靈活性和可插入性三個(gè)目標(biāo),提高了
    發(fā)表于 01-15 16:51 ?0次下載

    Viterbi譯碼器回溯算法實(shí)現(xiàn)

    該文介紹兩種Viterbi 譯碼器回溯譯碼算法,通過(guò)對(duì)這兩種算法硬件實(shí)現(xiàn)結(jié)構(gòu)上的優(yōu)化,給出了這兩種算法的FPGA 實(shí)現(xiàn)方法,比較
    發(fā)表于 05-28 15:18 ?33次下載
    Viterbi譯碼器<b class='flag-5'>回溯</b><b class='flag-5'>算法</b>實(shí)現(xiàn)

    一種數(shù)自適應(yīng)濾波算法

    一種數(shù)自適應(yīng)濾波算法,又需要的可以下來(lái)看看
    發(fā)表于 01-03 11:41 ?13次下載

    五大常用算法回溯

    回溯算法實(shí)際上一個(gè)類(lèi)似枚舉的搜索嘗試過(guò)程,主要是在搜索嘗試過(guò)程中尋找問(wèn)題的解,當(dāng)發(fā)現(xiàn)已不滿(mǎn)足求解條件時(shí),就“回溯”返回,嘗試別的路徑。
    的頭像 發(fā)表于 05-02 16:50 ?5981次閱讀
    五大常用<b class='flag-5'>算法</b>之<b class='flag-5'>回溯</b>法

    數(shù)獨(dú)算法概述和數(shù)獨(dú)的計(jì)算機(jī)和并行求解

    本文檔的主要內(nèi)容詳細(xì)介紹的是數(shù)獨(dú)算法概述和數(shù)獨(dú)的計(jì)算機(jī)和并行求解主要內(nèi)容包括:1.數(shù)
    發(fā)表于 12-21 10:37 ?8次下載
    <b class='flag-5'>數(shù)</b><b class='flag-5'>獨(dú)</b><b class='flag-5'>算法</b>概述和數(shù)<b class='flag-5'>獨(dú)</b>的計(jì)算機(jī)和并行求解

    基于Arduino的電子數(shù)獨(dú)游戲的制作教程

     此按鈕將嘗試應(yīng)用4個(gè)公式來(lái)解決求解器中的當(dāng)前拼圖。它已被證明可以解決所有“簡(jiǎn)單”,“中等”以及最多并包括大多數(shù)“硬”等級(jí)的數(shù)獨(dú)謎題。它不會(huì)完全解決所有數(shù)獨(dú)游戲,但它會(huì)給你一個(gè)良好的開(kāi)
    的頭像 發(fā)表于 09-03 15:37 ?3281次閱讀
    基于Arduino的電子<b class='flag-5'>數(shù)</b><b class='flag-5'>獨(dú)</b>游戲的制作教程

    關(guān)于回溯算法的介紹與運(yùn)用

    本文就來(lái)看一道非常經(jīng)典的回溯算法問(wèn)題,子集劃分問(wèn)題,可以幫你更深刻理解回溯算法的思維,得心應(yīng)手地寫(xiě)出回溯函數(shù)。
    的頭像 發(fā)表于 03-25 13:42 ?1673次閱讀

    如何用回溯算法來(lái)解決數(shù)獨(dú)問(wèn)題

    算法的核心思路非常非常的簡(jiǎn)單,就是對(duì)每一個(gè)空著的格子窮舉 1 到 9,如果遇到不合法的數(shù)字(在同一行或同一列或同一個(gè) 3×3 的區(qū)域中存在相同的數(shù)字)則跳過(guò),如果找到一個(gè)合法的數(shù)字,則繼續(xù)窮舉下一個(gè)空格子。
    的頭像 發(fā)表于 04-26 14:47 ?4891次閱讀

    回溯算法技巧分析

    如果你不理解這三個(gè)詞語(yǔ)的解釋?zhuān)瑳](méi)關(guān)系,我們后面會(huì)用「全排列」和「N 皇后問(wèn)題」這兩個(gè)經(jīng)典的回溯算法問(wèn)題來(lái)幫你理解這些詞語(yǔ)是什么意思,現(xiàn)在你先留著印象。
    的頭像 發(fā)表于 04-19 11:00 ?684次閱讀
    <b class='flag-5'>回溯</b><b class='flag-5'>算法</b>技巧分析
    主站蜘蛛池模板: 欧美色碰碰碰免费观看长视频| 女人张开腿让男人桶免费网站| 3344免费播放观看视频| 久久系列| 1024手机免费看| 在厨房乱子伦在线观看| 清朝荒淫牲艳史在线播放| 天天操天天噜| 天堂8资源8在线| 乱人伦一区二区三区| 成人精品第一区二区三区| 中文字幕一区二区视频| 九月婷婷亚洲综合在线| 亚洲伊人久久大香线蕉结合| 天堂网. www在线资源| 精品女同同性视频很黄很色| 最新人妖shemaletube人妖 | 狠狠色丁香婷婷综合久久来| 成人a毛片视频免费看| 五月天婷婷网亚洲综合在线| 黄a免费| 国产农村乱色xxxx| 真实女人寂寞偷人视频| 色网站在线视频| 久久亚洲一级毛片| 伊人91在线| 激情综合激情五月| 三级aa久久| 网友偷自拍原创区| 久久综合色区| 午夜伦y4480影院中文字幕| 免费中国jlzzjlzz在线播放| 黄色伊人网| 亚洲精品乱码久久久久久蜜桃图片| 日本免费大黄在线观看| 国产精品 色| 久久影视一区| 男男生子大肚play做到生| 色色色色网| 大色视频| 亚洲aa视频|