算法——回溯算法

一、如何理解回溯算法

回溯的处理思想,有点类似枚举搜索。枚举所有的解,找到满足期望的解。为了有规律地枚举所有可能的解,避免遗漏和重复,把问题求解的过程分为多个阶段。每个阶段都会面对一个岔咯口,先随意选择一条路走,当发现这条路走不通的时候(不符合期望的解),就回退到上一个岔路口,另选一种走法继续走。

二、回溯算法的经典应用

1、八皇后问题

有一个 8x8 的棋盘,希望往里放 8 个棋子(皇后),每个棋子所在的行、列、对角线都不能有另一个棋子。



把这个问题分成8个阶段,依次钭8个棋子放到第一行、第二行......第八行。在放置的过程中不停的检查当前的方法,是否满足要求。如果满足,则跳到下一行继续放置棋子;如果不满足,那就再换一种方法,继续尝试。

回溯算法非常适合用递归来实现,代码实现如下:

// 存储皇后的位置,下标为行,值为列
    int[] res = new int[8];
    public void eightQueen(int row) {
        // 已经完成
        if (row == 8) {
            // 打印已经放好的位置
            printQueen();
            return;
        }
        for (int col = 0; col < 8; col++) {
            // 判断该列是否可以放置
            if (isOk(row, col)) {
                res[row] = col;
                // 继续放下一行
                eightQueen(row + 1);
            }
        }
    }

    // 判断是否符合要求放置棋子
    private boolean isOk(int row, int col) {
        for (int i = row - 1; i >= 0; i--) {
            // 如果该列有棋子,不符合要求
            if (res[i] == col) {
                return false;
            }
            // 如果该位置对角线上有棋子,不符合要求;
            // 两个位置的行,列相减的绝对值相等,说明是在同条对角线上
            if (Math.abs(row - i) == Math.abs(col - res[i])) {
                return false;
            }
        }
        return true;
    }

    // 打印res数据,矩阵显示
    private void printQueen() {
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                if (res[row] == col) {
                    System.out.print("Q ");
                } else {
                    System.out.print("* ");
                }
            }
            System.out.println();
        }
        System.out.println();
    }
2、0-1 背包

有一个背包,背包总的承载重量是Wkg。现在有 n 个物品,每个物品的重量不等,并且不可分割。我们期望选择几个几件物品,装到背包中。在不超过背包所能装载重量的前提下,如何让背包中物品的总重量最大?

对于每个物品来说,都有两种选择,装进背包或者不装进背包。对于 n 个物品来说,总的装法就有 2^n 种。可以用回溯的方法来不重复的穷举这 2^n 种装法。先把品依次排列,整个问题就分解成了 n 个阶段,每个阶段对应一个物品怎么选择。代码实现如下:

    // 背包所能承受物品总质量的最大值
    int maxW = Integer.MIN_VALUE;
    /**
     * 装载背包
     *
     * @param i     要装载的物品下标
     * @param cw    当前已经装载的重量
     * @param items 物品数组
     * @param n     物品的数量
     * @param w     背包承载重量
     */
    public void loadBag(int i, int cw, int[] items, int n, int w) {
        if (cw == w || i == n) { // 装载的重量已经到达背包承受重量,或者物品已经装完了,可以退出
            if (cw > maxW) { // 记录装载的重量
                maxW = cw;
            }
            return;
        }
        // 不装当前物品
        loadBag(i + 1, cw, items, n, w);
        // 装当前物品,不过不能超过w
        if (cw + items[i] <= w) {
            loadBag(i + 1, cw + items[i], items, n, w);
        }
    }
3、正则表达式

正则表达式里最重要的一种算法思想就是回溯。
正则表达式中,最重要的就是通配符,通配符结合在一起,可以表达非常丰富的语义。 我们现在假设正则表达式中保包含 “ * ” 和 “ ? ” 这两种通配符,并且对这两个通配符的语义稍微做些改变,“ * ” 匹配任意多个(大于等于0个)任意字符,“ ? ” 匹配零个或者一个任意字符。基于以上背景假设,判断一个给定的文本,能否跟给定的正则表达式匹配?

我们依次考察正则表达式中的每个字符,当是非通配符时,直接跟文本的字符进行匹配,如果相同,则继续往下处理;如果不同,则回溯; 当遇到特殊字符时,有多种处理方式,也就是所谓的岔路吕,比如 “ * ”,有多种匹配方案,可以匹配任意个文本串中的字符,我们就先阴间的选择一种匹配方案,然后继续考察剩下的字符。如果中途发现无法继续匹配下去了,我们就回到这个岔路吕,重新选择一种匹配方案,再继续匹配剩下的字符。代码实现如下

/**
 * 正则表达式
 *
 * @Author YBF
 * @Date 2023/5/17
 */
public class Pattern {

    // 正则表达式
    private char[] pattern;
    // 正则表达式长度
    private int plen;
    // 匹配标识
    private boolean matched = false;

    public Pattern(char[] pattern, int plen) {
        this.pattern = pattern;
        this.plen = plen;
    }

    /**
     * 匹配字符串
     * @param text 文本
     * @param tlen 文本长度
     * @return 是否匹配
     */
    public boolean match(char[] text, int tlen) {
        matched = false;
        rmatch(0, 0, text, tlen);
        return matched;
    }

    /**
     * 正则表达式匹配
     *
     * @param ti   文本下标
     * @param pj   正则表达式下标
     * @param text 文本
     * @param tlen 文本长度
     */
    private void rmatch(int ti, int pj, char[] text, int tlen) {
        // 如果已经匹配了,就不用继续下去了
        if (matched) return;
        // 正则表达式到结尾了
        if (pj == plen) {
            // 文本也到结尾了,匹配上了
            if (ti == tlen) {
                matched = true;
            }
            return;
        }

        // 如果正则表达式当前字符是*
        if (pattern[pj] == '*') {
            // 匹配比较文本字符与下一个正现表达式下一个字符
            for (int i = ti; i < tlen; i++) {
                rmatch(i, pj + 1, text, tlen);
            }
        } else if (pattern[pj] == '?') { // 如果正则表达式当前字符是 ?
            // 匹配 0 个字符
            rmatch(ti, pj + 1, text, tlen);
            // 匹配 1 个字符
            rmatch(ti + 1, pj + 1, text, tlen);
        } else if (ti < tlen && text[ti] == pattern[pj]) {  // 正则表达式是普通字符,与文本字符相同往下继续匹配
            rmatch(ti + 1, pj + 1, text, tlen);
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 回溯思想 回溯算法的思想非常好理解,之前我们也使用回溯的思想完成了图的深度优先搜索。简单来说,回溯的思想是这样的:...
    天命_风流阅读 706评论 0 1
  • 如何理解“回溯算法”? 在我们的一生中,会遇到很多重要的岔路口。在岔路口上,每个选择都会影响我们今后的人生。有的人...
    青漾阅读 401评论 0 0
  • 点赞关注,不再迷路,你的支持对我意义重大!🔥 Hi,我是丑丑。本文「数据结构 & 算法」| 导读 —— 登高博见[...
    彭旭锐阅读 2,251评论 0 19
  • 回溯算法 回溯算法介绍   回溯算法并不是非常复杂的算法, 很多人接触过但是未必知道这是回溯算法; 典型的回溯算法...
    突击手平头哥阅读 168评论 0 1
  • 如果人生可以让你重新选择,以消耗十年寿命的代价,你会想回到哪一年? 这个问题,我,没有答案。因为这个问题,没有答案...
    StevenHD阅读 319评论 0 0