<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從電影《蝴蝶效應(yīng)》中學(xué)習(xí)回溯算法的核心思想

          共 5795字,需瀏覽 12分鐘

           ·

          2021-06-23 14:39

          點(diǎn)擊上方視學(xué)算法”,選擇加"星標(biāo)"或“置頂

          重磅干貨,第一時(shí)間送達(dá)

          關(guān)注我們丨文末贈(zèng)書(shū)


          深度優(yōu)先搜索算法利用的是回溯算法思想。這個(gè)算法思想非常簡(jiǎn)單,但應(yīng)用卻 非常廣泛。它除用來(lái)指導(dǎo)像深度優(yōu)先搜索這種經(jīng)典的算法設(shè)計(jì)之外,還可以用在很多實(shí)際的軟件開(kāi)發(fā)場(chǎng)景中,如正則表達(dá)式匹配、編譯原理中的語(yǔ)法分析等。除此之外,很多經(jīng)典的數(shù)學(xué)問(wèn)題也可以用回溯算法解決,如數(shù)獨(dú)、八皇后、0-1背包、圖的著色、旅行商、求全排列等。在本文,我們就來(lái)學(xué)習(xí)一下回溯算法思想。


          — 01 —

          如何理解回溯算法

          在我們的一生中,會(huì)遇到很多重要的“岔路口”。在人生的岔路口,每個(gè)選擇都會(huì)影響我們今后的人生。有的人在每個(gè)“岔路口”都能做出正確的選擇,生活、事業(yè)可能達(dá)到了一個(gè)很高的高度;而有的人一路選錯(cuò),最后可能碌碌無(wú)為。如果人生可以量化,那么如何才能在人生的岔路口做出正確的選擇,讓自己的人生“最優(yōu)”呢?我們可以借助貪心算法,在每次面對(duì)人生岔路口的時(shí)候,都做出當(dāng)下看起來(lái)最優(yōu)的選擇,期望這一組局部最優(yōu)選擇可以使得我們的人生達(dá)到全局“最優(yōu)”。

          但是,貪心算法并不一定能得到最優(yōu)解。那么,有沒(méi)有其他辦法能得到最優(yōu)解呢?2004年,著名的電影《蝴蝶效應(yīng)》上映,該部電影講的就是主人公為了達(dá)到自己的目標(biāo),一直通過(guò)回溯的方法,回到童年,在關(guān)鍵的人生岔路口,重新做選擇。當(dāng)然,這只是科幻電影,我們的人生是無(wú)法倒退的,但這其中蘊(yùn)含的思想其實(shí)就是回溯算法。回溯算法一般用到與“搜索”有關(guān)的問(wèn)題上。不過(guò)這里說(shuō)的搜索,并不是狹義地指圖的搜索,而是在一組可能的解中搜索滿(mǎn)足期望的解。回溯的處理思想有點(diǎn)類(lèi)似枚舉(或窮舉)。

          枚舉所有的解,找出其中滿(mǎn)足期望的解。為了有規(guī)律地枚舉所有可能的解,避免遺漏和重復(fù),我們把問(wèn)題求解的過(guò)程分為多個(gè)階段。每個(gè)階段都會(huì)面對(duì)一個(gè)“岔路口”,我們先隨意選一條路走,當(dāng)發(fā)現(xiàn)這條路走不通的時(shí)候(不符合期望的解),就回退到上一個(gè)“岔路口”,另選一種走法繼續(xù)走。

          — 02 —
          八皇后問(wèn)題
          我們來(lái)看一個(gè)有關(guān)回溯的經(jīng)典例子:八皇后問(wèn)題,以進(jìn)一步解釋回溯算法思想。有一個(gè)8×8的棋盤(pán),我們往里放 8 個(gè)棋子(皇后),要求每個(gè)棋子所在的行、列、對(duì)角線(xiàn)都不能有另外一個(gè)棋子。如圖1所示,左側(cè) 圖是符合要求的放法,右側(cè)圖是不符合要求的放 法。
          八皇后問(wèn)題就是期望找到所有滿(mǎn)足這種要求的放棋子方式。我們把求解這個(gè)問(wèn)題的過(guò)程劃分成8個(gè)階段:在第1行放置棋子、在第2行放置棋子、在第3行放置棋子……在第8行放置棋子。在放置的過(guò)程中,我們不停地檢查當(dāng)前的放法是否滿(mǎn)足要求。如果滿(mǎn)足,則跳到下一行繼續(xù)放置棋子;如果不滿(mǎn)足,就換一種放法,繼續(xù)嘗試?;厮菟惴ū容^適合用遞歸代碼實(shí)現(xiàn)。

          (1)八皇后問(wèn)題中符合要求的放法和不符合要求的放法

          八皇后問(wèn)題的遞歸代碼實(shí)現(xiàn)如下所示:

          int[] result = new int[8];//下標(biāo)表示行,值表示皇后存儲(chǔ)在哪一列public void cal8queens(int row) { //調(diào)用方式:cal8queens(0);  if (row == 8) { //8個(gè)棋子都放置好了,輸出結(jié)果 printQueens(result); return; //8行棋子都放好了,已經(jīng)沒(méi)法再往下遞歸了,因此返回 }  for (int column = 0; column < 8; ++column) { //每一行都有8種放法    if (isOk(row, column)) { //有些放法不滿(mǎn)足要求      result[row] = column; //第row行的棋子放到了column列      cal8queens(row+1); //考察下一行 } }}//判斷row行、column列放置是否合適private boolean isOk(int row, int column) { int leftup = column - 1, rightup = column + 1;  for (int i = row-1; i >= 0; --i) { //逐行往上考察每一行    if (result[i] == column) return false; //第i行、第column列有棋子    if (leftup >= 0) { //考察左上對(duì)角線(xiàn):第i行、第leftup列有棋子嗎? if (result[i] == leftup) return false; }    if (rightup < 8) { //考察右上對(duì)角線(xiàn):第i行、第rightup列有棋子嗎? if (result[i] == rightup) return false; } --leftup; ++rightup; } return true;}private void printQueens(int[] result) { //輸出一個(gè)二維矩陣 for (int row = 0; row < 8; ++row) { for (int column = 0; column < 8; ++column) { if (result[row] == column) System.out.print("Q "); else System.out.print("* "); } System.out.println(); } System.out.println();}

          — 03 —

          0-1 背包問(wèn)題

          0-1背包是一個(gè)非常經(jīng)典的算法問(wèn)題,很多場(chǎng)景可以抽象成這個(gè)問(wèn)題模型。這個(gè)問(wèn)題的經(jīng)典解法是動(dòng)態(tài)規(guī)劃,不過(guò)還有一種簡(jiǎn)單但沒(méi)有那么高效的解法,就是本文講的回溯算法。
          如何用回溯法解決這個(gè)問(wèn)題。
          0-1背包問(wèn)題有很多變體,這里介紹一種比較基礎(chǔ)的:假設(shè)有一個(gè)背包,可承載的最大重量是Wkg?,F(xiàn)在有n個(gè)物品,每個(gè)物品的重量不等,并且不可分割。我們期望選擇幾件物品裝到背包中。在不超過(guò)背包最大承載重量的前提下,如何讓背包中的物品總重量最大?實(shí)際上,在貪心算法時(shí),我們已經(jīng)講過(guò)一個(gè)背包問(wèn)題了。
          不過(guò),那里講的物品(豆子)是可以分割的,允許將某個(gè)物品的一部分裝到背包中。對(duì)于本文講的這個(gè)背包問(wèn)題,物品是不可分割的,要么裝要么不裝,因此稱(chēng)為0-1背包問(wèn)題。顯然,這個(gè)問(wèn)題已經(jīng)無(wú)法通過(guò)貪心算法來(lái)解決了?,F(xiàn)在我們來(lái)看一下,如何用回溯算法來(lái)解決。對(duì)于每個(gè)物品,都有兩種選擇:裝進(jìn)背包或者不裝進(jìn)背包。對(duì)于n個(gè)物品,總的裝法就有2n種,從這些裝法中選出總重量小于或等于Wkg并且最接近Wkg的。
          不過(guò),如何才能不重復(fù)地窮舉出這2n種裝法呢?這里就可以用到回溯算法思想。我們把物品依次排列,整個(gè)問(wèn)題的求解過(guò)程就分解為了n個(gè)階段,每個(gè)階段對(duì)應(yīng)一個(gè)物品怎么選擇。首先,對(duì)第一個(gè)物品進(jìn)行處理,選擇裝進(jìn)去或者不裝進(jìn)去,然后遞歸地處理剩下的物品。對(duì)于該問(wèn)題的處理思路,描述起來(lái)很費(fèi)勁,我們直接看如下所示的代碼。
          這里用到了搜索剪枝的技巧,當(dāng)發(fā)現(xiàn)已經(jīng)選擇的物品的總重量超過(guò)Wkg之后,我們就停止繼續(xù)探測(cè)剩下的物品。
          public int maxW = Integer.MIN_VALUE; //存儲(chǔ)背包中物品總重量的最大值//cw表示當(dāng)前已經(jīng)裝進(jìn)去的物品的重量和;i表示考察到哪個(gè)物品了//w表示背包可以承載的最大重量;items表示每個(gè)物品的重量;n表示物品個(gè)數(shù)//假設(shè)背包可承受重量為100,物品個(gè)數(shù)為10,物品重量存儲(chǔ)在數(shù)組a中,//那么可以這樣調(diào)用函數(shù):f(0, 0, a, 10, 100)public void f(int i, int cw, int[] items, int n, int w) {  // cw==w表示裝滿(mǎn)了;i==n表示已經(jīng)考察完所有的物品 if (cw == w || i == n) { if (cw > maxW) maxW = cw; return; } f(i+1, cw, items, n, w); if (cw + items[i] <= w) { //沒(méi)有超過(guò)背包可以承載的最大重量 f(i+1,cw + items[i], items, n, w); }}

          — 04 —

          正則表達(dá)式匹配問(wèn)題

          講完了0-1背包問(wèn)題,我們?cè)賮?lái)看另外一個(gè)例子:正則表達(dá)式匹配。對(duì)于軟件工程師,在平時(shí)的開(kāi)發(fā)中,或多或少用過(guò)正則表達(dá)式。其實(shí),正則表達(dá)式里非常 重要的一種算法思想就是回溯。在正則表達(dá)式中,最重要的就是通配符。利用通配符可以表達(dá)非常豐富的語(yǔ)義。為了方便講解,我們對(duì)正則表達(dá)式稍加簡(jiǎn)化,假設(shè)正則表達(dá)式中只包含“*”和“?”這兩種通配符,并且對(duì)這兩個(gè)通配符的語(yǔ)義稍微做些改變。
          其中,“*”匹配任意多個(gè)(大于或等于0個(gè))任意字符,“?”匹配0個(gè)或者1個(gè)任意字符?;谝陨媳尘凹僭O(shè),我們來(lái)探討一下,如何用回溯算法判斷一個(gè)給定的文本能否與給定的正則表達(dá)式匹配?我們依次考察正則表達(dá)式中的每個(gè)字符,如果是非通配符,就直接與文本串中的字符進(jìn)行匹配,如果相同,則繼續(xù)往下處理;如果不同,則回溯。
          如果遇到的是特殊字符,就有多種處理方式,也就是所謂的“岔路口”,如“*”可以匹配任意個(gè)文本串中的字符,我們就先隨意地選擇一種匹配方案,然后繼續(xù)考察剩下的字符。
          如果中途發(fā)現(xiàn)無(wú)法繼續(xù)匹配下去,就再回到這個(gè)“岔路口”,重新選擇一種匹配方案,然后繼續(xù)匹配剩下的字符。我們將上述處理過(guò)程“翻譯”成代碼,如下所示。
          public class Pattern { private boolean matched = false;  private char[] pattern; //正則表達(dá)式  private int plen; //正則表達(dá)式的長(zhǎng)度 public Pattern(char[] pattern, int plen) { this.pattern = pattern; this.plen = plen; }  public boolean match(char[] text, int tlen) { //文本串及其長(zhǎng)度 matched = false rmatch(0, 0, text, tlen); return matched; } private void rmatch(int ti, int pj, char[] text, int tlen) {    if (matched) return; //如果已經(jīng)匹配,就不要繼續(xù)遞歸了    if (pj == plen) { //正則表達(dá)式到結(jié)尾了      if (ti == tlen) matched = true; //文本串也到結(jié)尾了 return; }    if (pattern[pj] == '*') { //*匹配任意個(gè)字符 for (int k = 0; k <= tlen-ti; ++k) { rmatch(ti+k, pj+1, text, tlen); }    } else if (pattern[pj] == '?') { //?匹配0個(gè)或者1個(gè)字符 rmatch(ti, pj+1, text, tlen); rmatch(ti+1, pj+1, text, tlen);    } else if (ti < tlen && pattern[pj] == text[ti]) { //純字符匹配才行 rmatch(ti+1, pj+1, text, tlen); } }}

          — 05 —

          內(nèi)容小結(jié)

          回溯算法思想非常簡(jiǎn)單,大部分情況下,用來(lái)解決廣義的搜索問(wèn)題,也就是從一組可能的解中選出一個(gè)滿(mǎn)足要求的解。回溯算法非常適合用遞歸來(lái)實(shí)現(xiàn),在實(shí)現(xiàn)的過(guò)程中,剪枝操作是提高搜索效率的一種技巧。利用剪枝,我們可以提前終止搜索不能滿(mǎn)足要求的解的過(guò)程。
          盡管回溯算法的原理非常簡(jiǎn)單,但可以解決很多問(wèn)題,如深度優(yōu)先搜索、八皇后、0-1背包、圖的著色、旅行商、數(shù)獨(dú)、求全排列和正則表達(dá)式匹配等。如果讀者感興趣,那么可以自己研究一下這些經(jīng)典的問(wèn)題,最好還能用代碼實(shí)現(xiàn)。對(duì)于這幾個(gè)問(wèn)題,如果讀者都能順利地用代碼實(shí)現(xiàn),那么說(shuō)明讀者基本掌握了回溯算法。
          本文摘自《數(shù)據(jù)結(jié)構(gòu)與算法之美》,如果你想了解的更多,請(qǐng)查看本書(shū)。
          ?  NO.1  ?
          數(shù)據(jù)結(jié)構(gòu)與算法之美

          • 點(diǎn)擊上圖,即可購(gòu)買(mǎi)《數(shù)據(jù)結(jié)構(gòu)與算法之美》!

          本書(shū)結(jié)合實(shí)際應(yīng)用場(chǎng)景講解數(shù)據(jù)結(jié)構(gòu)和算法,涵蓋常用、常考的數(shù)據(jù)結(jié)構(gòu)和算法的原理講解、代碼實(shí)現(xiàn)和應(yīng)用場(chǎng)景等。

          本書(shū)分為11章。第1章介紹復(fù)雜度分析方法。第2章介紹數(shù)組、鏈表、棧和隊(duì)列這些基礎(chǔ)的線(xiàn)性表數(shù)據(jù)結(jié)構(gòu)。第3章介紹遞歸編程技巧、8種經(jīng)典排序、二分查找及二分查找的變體問(wèn)題。第4章介紹哈希表、位圖、哈希算法和布隆過(guò)濾器。第5章介紹樹(shù)相關(guān)的數(shù)據(jù)結(jié)構(gòu),包括二叉樹(shù)、二叉查找樹(shù)、平衡二叉查找樹(shù)、遞歸樹(shù)和B+樹(shù)。第6章介紹堆,以及堆的各種應(yīng)用,包括堆排序、優(yōu)先級(jí)隊(duì)列、求TopK、求中位數(shù)和求百分位數(shù)。第7章介紹跳表、并查集、線(xiàn)段樹(shù)和樹(shù)狀數(shù)組這些比較高級(jí)的數(shù)據(jù)結(jié)構(gòu)。第8章介紹字符串匹配算法,包括BF算法、RK算法、BM算法、KMP算法、Trie樹(shù)和AC自動(dòng)機(jī)。第9章介紹圖及相關(guān)算法,包括深度優(yōu)先搜索、廣度優(yōu)先搜索、拓?fù)渑判颉ijkstra算法、Floyd算法、A*算法、最小生成樹(shù)算法、最大流算法和最大二分匹配等。第10章介紹4種算法思想,包括貪心、分治、回溯和動(dòng)態(tài)規(guī)劃。第11章介紹4個(gè)經(jīng)典項(xiàng)目中的數(shù)據(jù)結(jié)構(gòu)和算法的應(yīng)用,包括Redis、搜索引擎、鑒權(quán)限流和短網(wǎng)址服務(wù)。

          另外,附錄A為書(shū)中的思考題的解答。盡管本書(shū)的大部分代碼采用Java語(yǔ)言編寫(xiě),但本書(shū)講解的知識(shí)與具體編程語(yǔ)言無(wú)關(guān),因此,本書(shū)不但適合各種類(lèi)型的研發(fā)工程師,而且可以作為高校計(jì)算機(jī)相關(guān)專(zhuān)業(yè)師生的學(xué)習(xí)用書(shū)和培訓(xùn)學(xué)校的教材。

          歡迎加入本書(shū)讀書(shū)會(huì)社群,打卡學(xué)習(xí)


          —END—


          點(diǎn)個(gè)在看 paper不斷!

          瀏覽 23
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲AV无码乱码国产精品 | 精品A√| 亚洲清晰视频 | 欧美第一页福利 | www.男人天堂网 |