算法學習:回溯和剪枝

一典尾、理論

回溯

  1. 本質(zhì):和深度優(yōu)先遍歷思想是一致的身冀,都是遞歸的應用睹耐;搜索空間可以理解成一棵樹辐赞,需要自頂向下不斷枚舉出所有的情況。
  2. 寫法的關鍵:循環(huán)和遞歸硝训。
  • for循環(huán)的作用在于另尋它路响委,可以逐個選擇當前節(jié)點下的所有可能往下走下去的分支路徑。
  • 遞歸可以實現(xiàn)一條路走到黑和回退一步窖梁,把遞歸放在for循環(huán)內(nèi)部赘风,那么for每一次的循環(huán),都在給出一個路徑后進入遞歸窄绒,繼續(xù)往下走贝次。
  1. 代碼模版
void backtracking(參數(shù)) {
    if (終止條件) {
        存放結(jié)果;
        return;
    }

    for (選擇:本層集合中元素(樹中節(jié)點孩子的數(shù)量就是集合的大小)) {
        處理節(jié)點;
        backtracking(路徑彰导,選擇列表); // 遞歸
        回溯蛔翅,撤銷處理結(jié)果
    }
}

剪枝

  1. 定義:剪枝算法是排除搜索空間中不可能包含最優(yōu)解的部分,從而減少搜索時間以及空間復雜度位谋。
  2. 實現(xiàn)方式:
    A. 可行性剪枝
    利用問題的特性進行剪枝山析;在搜索過程中,根據(jù)問題的特點掏父,排除不符合條件的搜索分支笋轨。
    B. 排除等效冗余
    當幾個枝椏具有完全相同的效果的時候,只選擇其中一個走就可以了赊淑。
    C. 最優(yōu)性剪枝
    所謂最優(yōu)性剪枝爵政,是在我們用搜索方法解決最優(yōu)化問題的時候的一種常用剪枝方法。當搜到一半的時候陶缺,發(fā)現(xiàn)比已經(jīng)搜索到的最優(yōu)解差钾挟,則該方案肯定是不行的,即刻停止搜索饱岸,進行回溯掺出。
    D. 順序剪枝
    普遍來講,搜索的順序是不固定的苫费,對一個問題來講汤锨,算法可以進入搜索樹的任意的一個子節(jié)點。但假如我們要搜索一個最小值百框,而非要從最大值存在的那個節(jié)點開搜闲礼,就可能存在搜索到最后才出解。而我們從最小的節(jié)點開搜很可能馬上就出解铐维。這就是順序剪枝的一個應用柬泽。一般來講,有單調(diào)性存在的搜索問題可以和貪心思想結(jié)合方椎,進行順序剪枝聂抢。
    E. 記憶化搜索
    記憶化搜索其實是搜索的另外一個分支。在這里簡單介紹一下記憶化的原理:就是記錄搜索的每一個狀態(tài)棠众,當重復搜索到相同的狀態(tài)的時候直接返回琳疏。

二、算法實踐

問題鏈接

37. 解數(shù)獨

問題描述

編寫一個程序闸拿,通過填充空格來解決數(shù)獨問題空盼。
數(shù)獨的解法需 遵循如下規(guī)則:
數(shù)字 1-9 在每一行只能出現(xiàn)一次。
數(shù)字 1-9 在每一列只能出現(xiàn)一次新荤。
數(shù)字 1-9 在每一個以粗實線分隔的 3x3 宮內(nèi)只能出現(xiàn)一次揽趾。(請參考示例圖)
數(shù)獨部分空格內(nèi)已填入了數(shù)字,空白格用 '.' 表示苛骨。

示例

輸入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
輸出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]

解題思路

  1. 遍歷整個數(shù)獨篱瞎,獲取數(shù)字在行苟呐、列、宮格中的出現(xiàn)情況俐筋,以及空閑位置牵素。
  2. 用遞歸的方法,給空閑位置挨個放數(shù)澄者,放數(shù)的同時校驗行笆呆、列、宮格內(nèi)的重復性粱挡,如果發(fā)生重復赠幕,就剪掉并回溯。

代碼示例(JAVA)

class Solution {
    // 表示行询筏、列榕堰、宮格中數(shù)字的出現(xiàn)情況
    private boolean[][] row = new boolean[9][9];
    private boolean[][] column = new boolean[9][9];
    private boolean[][][] grid = new boolean[3][3][9];
    // 找出需要放入數(shù)字的位置
    private List<int[]> empty = new ArrayList<int[]>();

    public void solveSudoku(char[][] board) {
        // 遍歷整個數(shù)獨,獲取數(shù)字在行屈留、列局冰、宮格中的出現(xiàn)情況,以及空閑位置
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') {
                    empty.add(new int[]{i, j});
                } else {
                    int index = board[i][j] - '0' - 1;
                    row[i][index] = column[j][index] = grid[i / 3][j / 3][index] = true;
                }
            }
        }

        dfs(board, 0);
    }

    public boolean dfs(char[][] board, int pos) {
        // 遞歸終止條件
        if (pos == empty.size()) {
            return true;
        }

        int[] target = empty.get(pos);
        int i = target[0], j = target[1];
        for (int k = 0; k < 9; ++k) {
            // 經(jīng)過校驗灌危,可以放入k康二;校驗失敗就不要了(剪枝)
            if (!row[i][k] && !column[j][k] && !grid[i /3][j / 3][k]) {
                // 放入k,并更新數(shù)字出現(xiàn)情況勇蝙,且繼續(xù)遞歸
                board[i][j] = (char) (k + '0' + 1);
                row[i][k] = column[j][k] = grid[i / 3][j / 3][k] = true;
                // 遞歸到下一層
                if (dfs(board, pos + 1)) {
                    return true;
                }
                // 失敗沫勿,回溯
                row[i][k] = column[j][k] = grid[i / 3][j / 3][k] = false;
            }
        }
        // 找不到可以放入的k,return false
        return false;
    }
}

參考文章

數(shù)據(jù)結(jié)構(gòu)與算法之剪枝算法
什么是算法中的剪枝味混?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末产雹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子翁锡,更是在濱河造成了極大的恐慌蔓挖,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馆衔,死亡現(xiàn)場離奇詭異瘟判,居然都是意外死亡,警方通過查閱死者的電腦和手機角溃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門拷获,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人减细,你說我怎么就攤上這事匆瓜。” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵驮吱,是天一觀的道長茧妒。 經(jīng)常有香客問我,道長糠馆,這世上最難降的妖魔是什么嘶伟? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任怎憋,我火速辦了婚禮又碌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绊袋。我一直安慰自己毕匀,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布癌别。 她就那樣靜靜地躺著皂岔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪展姐。 梳的紋絲不亂的頭發(fā)上躁垛,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音圾笨,去河邊找鬼教馆。 笑死,一個胖子當著我的面吹牛擂达,可吹牛的內(nèi)容都是我干的土铺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼板鬓,長吁一口氣:“原來是場噩夢啊……” “哼悲敷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起俭令,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤后德,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抄腔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓢湃,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年妓柜,在試婚紗的時候發(fā)現(xiàn)自己被綠了箱季。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡棍掐,死狀恐怖藏雏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤掘殴,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布赚瘦,位于F島的核電站,受9級特大地震影響奏寨,放射性物質(zhì)發(fā)生泄漏起意。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一病瞳、第九天 我趴在偏房一處隱蔽的房頂上張望揽咕。 院中可真熱鬧,春花似錦套菜、人聲如沸亲善。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛹头。三九已至,卻和暖如春戏溺,著一層夾襖步出監(jiān)牢的瞬間渣蜗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工旷祸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耕拷,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓肋僧,卻偏偏與公主長得像斑胜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嫌吠,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容