从全排列看回溯算法

从全排列看回溯算法

最近又刷起了算法,仿佛回到了大一时奋战到深夜场景,走上社会之初发现大学里学的都是啥玩意儿,工作中基本遇不到,各种数据结构都被封装的妥妥的根本不需要我们去操心,以至于越来越浮于表面。

现在觉得大学的课程是真功夫,是无数学者总结提炼的精华,是计算机从业人员是基本功,基本功不扎实很快就会遇到瓶颈,对算法与数据结构掌握与理解不透彻很难写出非常优秀的软件,亡羊补牢为时不晚,所以拿起旧书本回炉重造磨练自己基本功。 学习算法不仅会收获很多还会给你带来成就感。

下面用通俗的方式结合例子给大家介绍回溯算法

回溯算法框架

func backtrack(选择列表,路径) {
   if 结束条件 {
       得到一种结果
   }
   for i in 选择列表 {
      if 减支条件 {
         continue
      }
      选择列表加入路径
      backtrack(选择列表,路径)
      撤销选择
   }
}

这个先看不太懂没关系,读完全文可再返回阅读。 回溯算法本质就是一个多叉树遍历问题

我们以在袋子里抓球为力来解释一下上面几个名词。 假设袋子里三个球,抓一个那么就有三种选择,所以选择列表是:[1,2,3] , 如果你抓到1,那么[1]便是路径,对应的是树的树枝。

结束条件:比如你只抓一次,那么结束条件就是路径长度等于1
减支条件:比如抓完放回球,那么就没有减支条件,如果抓完不放回那么条件就是路径里如果已经存在就不再遍历。 很多时候不同的问题都是在减支条件这里做手脚,比如N皇后问题.
撤销选择:为啥要撤销选择?其实就是在遍历到叶子节点之后我们需要重新返回到父节点重新寻找其它路径

全排列

给定一个不幸串,输出它的全排列

先来看个最简单的场景:

袋子里有两个球,取出一个记下,放回袋子,再取一个,有多少种结果

输入:[1,2]
输出:[[1,1],[1,2],[2,1],[2,2]]

这样就得出一个二叉树:

lC59oR.jpg

所有到叶子节点的路径就是我们需要求解的解集,所以这个问题变成了一个多叉树遍历问题:

func tree(选择,路径){
   结束条件
   遍历分叉
     进入节点前干啥
     递归节点
     遍历节点后干啥
}

所谓回溯,就是在遍历节点后撤销我们选择路径中的选择,所以想一下第一次遍历:


lC5iJx.jpg

已经走到了一个叶子节点,这时我们已经得出一个解[1,1]并且已经把它存在res的结果中。

所以我们现在想从叶子节点A走回到B,下一步往C去走。这样在回溯到B之前路径是[1,1],回溯之后路径变成[1], 然后递归遍历到C时路径变成[1,2]得到第二个解

res [][]int
func tree(nums []int, track []int) {
   if len(track) == 2 //我们取两次 {
       // 取到了一个结果
       res = append(res,track)
   }
   for _,n := range nums {
      // 遍历选择列表,把选择列表加入到路径中,所以选择列表多长就是多少叉树
      track = append(track, n)
      tree(nums, track)
      track = track[:len(track) - 1] // 撤销路径最后一个选择,在此之前已经遍历到叶子节点并把解记录到了res中,因为递归时已经满足了结束条件
   }
}

有了回溯法,寥寥几行代码就解决了上面问题。下面来加大一下难度:

全排列

一串不重复的数字,输出其全排列,如:

输入:[1,2]
输出:[[1,2],[2,1]]

一眼就能看到结果是上面题目的子集,说明啥?多叉树被剪枝了! 如何剪枝?

很简单,满足一定条件啥也不做就行,不去选择->递归->撤销选择, 所以:

func tree(选择,路径){
   结束条件
   遍历分叉
     if 满足剪枝条件
        continue
     进入节点前干啥
     递归节点
     遍历节点后干啥
}

问题就简化为剪枝条件是啥?

显然特点就是已经出现在路径中的元素就不能再选择:

lC5pw9.jpg

代码其它部分不变,for循环里变成:

for _,n := range nums {
   if has(track,n) { //表示track列表中包含n
      continue
   }
   track = append(track, n)
   tree(nums, track)
   track = track[:len(track) - 1] // 撤销路径最后一个选择,在此之前已经遍历到叶子节点并把解记录到了res中,因为递归时已经满足了结束条件
}

轻松搞定

有重复元素的全排列

现在假设选择列表nums中有重复元素如[1,1,2,3]那又该怎么做?

聪明人立马会意识到,其它不变,只是剪枝条件发生了变化:

  1. 选择列表中的元素没有被遍历过
  2. 任何节点的树枝不能重复

要注意不能被重复剪枝,在判断是不是重复时不用考虑已经被剪枝的树枝

lC4xL4.jpg

所以最主要的是修改剪枝条件,但是判断某个元素是否被访问过我们需要引入一个数组来保存选择列表某个元素是否被访问

        if flag[i] {
            continue
        }
        if Has(num[:i], num[i], flag) {
            continue
        }

        track = append(track, num[i])
        flag[i] = true
        back(num, flag, track)
        track = track[:len(track)-1]
        flag[i] = false

func Has(a []int, b int, flag []bool) bool {
    l := len(a)
    if l == 0 {
        return false
    }
    for i := 0; i < l; i++ {
        if flag[i] {        // 细节,不用考虑已经被剪枝的树枝
            continue
        }
        if a[i] == b {
            return true
        }
    }

    return false
}

至此你已经掌握了回溯算法的精髓,然后就是活学活用推广到其它问题中。一定要动手再细细琢磨才能触类旁通。

N皇后问题

在一个N*N的棋盘上摆N个皇后,彼此不攻击对方的摆法。

有了回溯算法的基础此问题就变得简单了。

一行一行的放皇后,第一行就有N种放法,如此就又变成了一颗N叉树,思考三个核心元素: 选择列表是啥,路径是啥,剪枝条件是啥

  1. 选择列表就可以用一个N位数组
  2. 路径可以用二维数组
  3. 剪枝条件就变成放的位置横竖斜有没有皇后

如此问题便可解决,如果建议学完回溯算法再拿N皇后稳定巩固一下。
kubernetes一键HA

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

推荐阅读更多精彩内容

  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,640评论 0 89
  • 回溯法学习总结 回溯算法也是算法导论中常用的算法,回溯算法类似于暴力求解算法,经常用在求可能解的问题。下面我将从三...
    鱼鱼鱼三条鱼ii阅读 3,580评论 0 5
  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,730评论 0 13
  • 好久没有写博客了,有一年多了吧,想想那些能够安心码字的日子还甚是怀念,于是今晚无论外界条件怎样的恶劣,这一篇是一定...
    笔凡阅读 2,276评论 1 4
  • 逝去了过去的,珍惜不了现在的,有一些被隐藏的,有一些是被误会的,当我们走出校园,我们还可以任性吗?
    橙十四阅读 117评论 0 0