精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何用回溯算法來解決數獨問題

算法與數據結構 ? 來源:labuladong ? 作者:labuladong ? 2022-04-26 14:47 ? 次閱讀

經常拿回溯算法來說事兒的,無非就是八皇后問題和數獨問題了。那我們今天就通過實際且有趣的例子來講一下如何用回溯算法來解決數獨問題。

一、直觀感受

說實話我小的時候也嘗試過玩數獨游戲,但從來都沒有完成過一次。做數獨是有技巧的,我記得一些比較專業的數獨游戲軟件,他們會教你玩數獨的技巧,不過在我看來這些技巧都太復雜,我根本就沒有興趣看下去。

不過自從我學習了算法,多困難的數獨問題都攔不住我了。下面是我用程序完成數獨的一個例子:

372cdc3c-c3bd-11ec-bce3-dac502259ad0.gif

PS:GIF 可能出現 bug,若卡住點開查看即可,下同。

這是一個安卓手機中的數獨游戲,我使用一個叫做 Auto.js 的腳本引擎,配合回溯算法來實現自動完成填寫,并且算法記錄了執行次數。

在后文,我會給出該腳本的實現思路代碼以及軟件工具的下載,你也可以拿來裝13用

可以觀察到前兩次都執行了 1 萬多次,而最后一次只執行了 100 多次就算出了答案,這說明對于不同的局面,回溯算法得到答案的時間是不相同的。

那么計算機如何解決數獨問題呢?其實非常的簡單,就是窮舉嘛,下面我可視化了求解過程:

37639858-c3bd-11ec-bce3-dac502259ad0.gif

算法的核心思路非常非常的簡單,就是對每一個空著的格子窮舉 1 到 9,如果遇到不合法的數字(在同一行或同一列或同一個 3×3 的區域中存在相同的數字)則跳過,如果找到一個合法的數字,則繼續窮舉下一個空格子

對于數獨游戲,也許我們還會有另一個誤區:就是下意識地認為如果給定的數字越少那么這個局面的難度就越大。

這個結論對人來說應該沒毛病,但對于計算機而言,給的數字越少,反而窮舉的步數就越少,得到答案的速度越快,至于為什么,我們后面探討代碼實現的時候會講。

上一個 GIF 是最后一關 70 關,下圖是第 52 關,數字比較多,看起來似乎不難,但是我們看一下算法執行的過程:

378d7132-c3bd-11ec-bce3-dac502259ad0.gif

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

言歸正傳,下面我們就來具體探討一下如何用算法來求解數獨問題,順便說說我是如何可視化這個求解過程的

二、代碼實現

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

voidsolveSudoku(char[][]board);

輸入是一個9x9的棋盤,空白格子用點號字符.表示,算法需要在原地修改棋盤,將空白格子填上數字,得到一個可行解。

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

前文回溯算法詳解,已經寫過了回溯算法的套路框架,如果還沒看過那篇文章的,建議先看看

回憶剛才的 GIF 圖片,我們求解數獨的思路很簡單粗暴,就是對每一個格子所有可能的數字進行窮舉。對于每個位置,應該如何窮舉,有幾個選擇呢?

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

//對board[i][j]進行窮舉嘗試
voidbacktrack(char[][]board,inti,intj){
intm=9,n=9;
for(charch='1';ch<=?'9';ch++){
//做選擇
board[i][j]=ch;
//繼續窮舉下一個
backtrack(board,i,j+1);
//撤銷選擇
board[i][j]='.';
}
}

emmm,再繼續細化,并不是 1 到 9 都可以取到的,有的數字不是不滿足數獨的合法條件嗎?而且現在只是給j加一,那如果j加到最后一列了,怎么辦?

很簡單,當j到達超過每一行的最后一個索引時,轉為增加i開始窮舉下一行,并且在窮舉之前添加一個判斷,跳過不滿足條件的數字

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

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

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

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

//判斷board[r][c]是否可以填入n
booleanisValid(char[][]board,intr,intc,charn){
for(inti=0;i9;i++){
//判斷行是否存在重復
if(board[r][i]==n)returnfalse;
//判斷列是否存在重復
if(board[i][c]==n)returnfalse;
//判斷3x3方框是否存在重復
if(board[(r/3)*3+i/3][(c/3)*3+i%3]==n)
returnfalse;
}
returntrue;
}

emmm,現在基本上差不多了,還剩最后一個問題:這個算法沒有 base case,永遠不會停止遞歸。這個好辦,什么時候結束遞歸?顯然r == m的時候就說明窮舉完了最后一行,完成了所有的窮舉,就是 base case

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

最終代碼修改如下:

booleanbacktrack(char[][]board,inti,intj){
intm=9,n=9;
if(j==n){
//窮舉到最后一列的話就換到下一行重新開始。
returnbacktrack(board,i+1,0);
}
if(i==m){
//找到一個可行解,觸發basecase
returntrue;
}

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

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

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

booleanisValid(char[][]board,intr,intc,charn){
//見上文
}

現在可以回答一下之前的問題,為什么有時候算法執行的次數多,有時候少?為什么對于計算機而言,確定的數字越少,反而算出答案的速度越快

我們已經實現了一遍算法,掌握了其原理,回溯就是從 1 開始對每個格子窮舉,最后只要試出一個可行解,就會立即停止后續的遞歸窮舉。所以暴力試出答案的次數和隨機生成的棋盤關系很大,這個是說不準的。

那么你可能問,既然運行次數說不準,那么這個算法的時間復雜度是多少呢

對于這種時間復雜度的計算,我們只能給出一個最壞情況,也就是 O(9^M),其中M是棋盤中空著的格子數量。你想嘛,對每個空格子窮舉 9 個數,結果就是指數級的。

這個復雜度非常高,但稍作思考就能發現,實際上我們并沒有真的對每個空格都窮舉 9 次,有的數字會跳過,有的數字根本就沒有窮舉,因為當我們找到一個可行解的時候就立即結束了,后續的遞歸都沒有展開。

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

如果給定的數字越少,相當于給出的約束條件越少,對于計算機這種窮舉策略來說,是更容易進行下去,而不容易走回頭路進行回溯的,所以說如果僅僅找出一個可行解,這種情況下窮舉的速度反而比較快。

至此,回溯算法就完成了,你可以用以上代碼通過 LeetCode 的判題系統,下面我們來簡單說下我是如何把這個回溯過程可視化出來的。

三、算法可視化

讓算法幫我玩游戲的核心是算法,如果你理解了這個算法,剩下就是借助安卓腳本引擎 Auto.js 調 API 操作手機了,工具我都放在后臺了,你等會兒就可以下載。

用偽碼簡單說下思路,我可以寫兩個函數:

voidsetNum(Buttonb,charn){
//輸入一個方格,將該方格設置為數字n
}

voidcancelNum(Buttonb){
//輸入一個方格,將該方格上的數字撤銷
}

回溯算法的核心框架如下,只要在框架對應的位置加上對應的操作,即可將算法做選擇、撤銷選擇的過程完全展示出來,也許這就是套路框架的魅力所在:

for(charch='1';ch<=?'9';ch++){
Buttonb=newButton(r,c);
//做選擇
setNum(b,ch);
board[i][j]=ch;
//繼續窮舉下一個
backtrack(board,i,j+1);
//撤銷選擇
cancelNum(b);
board[i][j]='.';
}

以上思路就可以模擬出算法窮舉的過程:

37639858-c3bd-11ec-bce3-dac502259ad0.gif

--- EOF ---

審核編輯 :李倩

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 代碼
    +關注

    關注

    30

    文章

    4751

    瀏覽量

    68359
  • 回溯算法
    +關注

    關注

    0

    文章

    10

    瀏覽量

    6591

原文標題:搞懂回溯算法,我終于能做數獨了

文章出處:【微信號:TheAlgorithm,微信公眾號:算法與數據結構】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Pure path studio內能否自己創建一個component,實現特定的算法,例如LMS算法

    TLV320AIC3254EVM-K評估模塊, Pure path studio軟件開發環境。 問題:1.Pure path studio 內能否自己創建一個component,實現特定的算法
    發表于 11-01 08:25

    AFE76xx作為使用回送模式的單芯片寬帶中繼器

    電子發燒友網站提供《AFE76xx作為使用回送模式的單芯片寬帶中繼器.pdf》資料免費下載
    發表于 10-08 11:30 ?0次下載
    AFE76xx作為使<b class='flag-5'>用回</b>送模式的單芯片寬帶中繼器

    何用PMBus解碼UCD90xxx故障日志

    電子發燒友網站提供《如何用PMBus解碼UCD90xxx故障日志.pdf》資料免費下載
    發表于 09-25 10:04 ?0次下載
    如<b class='flag-5'>何用</b>PMBus解碼UCD90xxx故障日志

    RVBacktrace RISC-V極簡棧回溯組件

    RVBacktrace組件簡介一個極簡的RISC-V棧回溯組件。功能在需要的地方調用組件提供的唯一API,開始當前環境的棧回溯支持輸出addr2line需要的命令,使用addr2line進行棧回溯支持結合反匯編,棧
    的頭像 發表于 09-15 08:12 ?301次閱讀
    RVBacktrace RISC-V極簡棧<b class='flag-5'>回溯</b>組件

    回溯英特爾在跨越半個世紀的發展歷程

    我們以英特爾三位風云人物的三句名言為線索,回溯英特爾在跨越半個世紀的發展歷程中,如何利用芯片技術的力量,影響信息時代,開啟未來之門。
    的頭像 發表于 08-16 14:58 ?567次閱讀

    神經網絡如何用無監督算法訓練

    標記數據的處理尤為有效,能夠充分利用互聯網上的海量數據資源。以下將詳細探討神經網絡如何用無監督算法進行訓練,包括常見的無監督學習算法、訓練過程、應用及挑戰。
    的頭像 發表于 07-09 18:06 ?702次閱讀

    bp神經網絡算法的基本流程包括哪些

    BP神經網絡算法,即反向傳播神經網絡算法,是一種常用的多層前饋神經網絡訓練算法。它通過反向傳播誤差調整網絡的權重和偏置,從而實現對輸入數據的分類或回歸。下面詳細介紹BP神經網絡
    的頭像 發表于 07-04 09:47 ?493次閱讀

    工控主機的集顯和顯口怎么區分

    工控主機,作為工業控制領域的核心設備,其圖形處理能力對于整個系統的運行效率至關重要。在工控主機中,集成顯卡(集顯)和獨立顯卡(顯)是兩種常見的圖形處理方案。它們各自具有不同的特點和應用場景,因此,正確區分工控主機的集顯和顯接口顯得尤為重要。
    的頭像 發表于 05-21 18:19 ?1387次閱讀
    工控主機的集顯和<b class='flag-5'>獨</b>顯口怎么區分

    求助,關于STM32上開發函數調用堆棧回溯的問題求解

    1、stm32f1系列 2、上了FreeRTOS 3、想開發函數調用回溯功能 在編譯選項中增加了--use_frame_pointer,編程一個正常的程序(之前一直run的),測試發現,程序啟動即crash,請問有沒有高手之前遇到過?
    發表于 05-10 07:32

    三星明年起擬在芯片制造中利用回收氖氣

    據韓國《朝鮮經濟日報》披露,預計明年起,三星將在芯片生產環節啟用回收氖氣,成為全球率先采用該方法的企業。據悉,三星已經聯合當地一家材料公司研發設備,以便從激光廢料流中提取氖氣,然后進行提純處理,再投入使用。
    的頭像 發表于 03-08 13:50 ?459次閱讀

    石電容的特點及作用 石電容有極性嗎?石電容有分正負極嗎?

    石電容的特點及作用 石電容有極性嗎?石電容有分正負極嗎? 石電容是一種常見的電子元件。它主要由金屬氧化物半導體場效應晶體管(MOSFET)和電介質組成。
    的頭像 發表于 03-07 13:53 ?2998次閱讀

    石電容參數是什么?石電容和鉭電容區別在哪

    石電容參數是什么?石電容和鉭電容區別在哪? 石電容參數是指石電容器的一些重要參數,這些參數用于描述石電容器的性能和特性。
    的頭像 發表于 03-07 13:53 ?1692次閱讀

    接觸器不吸合,如何用萬用表判斷接觸器線圈的好壞?

    接觸器不吸合,如何用萬用表判斷接觸器線圈的好壞? 接觸器是電氣控制裝置中常見的一種,廣泛應用于電動機的起動、制動和轉向等電力系統中。然而,在長時間使用過程中,接觸器的線圈可能會出現老化、磨損等
    的頭像 發表于 02-18 14:52 ?2851次閱讀

    何用BUCK電路簡單實現一個可靠的負電源?

    何用BUCK電路簡單實現一個可靠的負電源?
    的頭像 發表于 12-05 15:12 ?763次閱讀
    如<b class='flag-5'>何用</b>BUCK電路簡單實現一個可靠的負電源?

    何用ADIsimADC完成ADC建模

    電子發燒友網站提供《如何用ADIsimADC完成ADC建模.pdf》資料免費下載
    發表于 11-28 10:36 ?2次下載
    如<b class='flag-5'>何用</b>ADIsimADC完成ADC建模