回溯

如何理解回溯

回溯可以理解成一个人在前进的过程中有无数个岔路口,经过一个岔路口,又有一个岔路口。每在一个岔路口选择一个道都会影响这个人之后的人生。
有的人在每一个岔路口都能做出十分正确的选择,所以这个人的生活和事业都达到了人生巅峰。而有的人一步,步步错,可能就是它最初的选择的那个岔路口就是错的,导致这个人就一致生活坎坷。
经典的回溯算法解决的问题很多,如八皇后、0-1背包问题、图的着色、旅行商问题、全排列问题。
下面挑几个例子来

八皇后问题

  • 问题:
    假设有一个 8x8 的棋盘,希望往整个棋盘放入8个棋子(皇后Q),每个棋子所在的行,列,对角线都不能有其他的棋子,下面的图,第一幅图就是符合条件的,而第二幅图就是不符合条件的,八皇后问题就是希望找到所有期望满足的放置棋子的方法。


    八皇后问题

    那么这个问题按照刚才的理解就可以变成这样。

  • 问题抽象
    一共有8个岔路口,每个岔路口就是一行,每行随便选择一个地方放置棋子。但是每放一个棋子,就需要检查当前的状态还满足八皇后问题吗。不满足直接pass,满足接着放下一行吧。

这是简化版的三皇后问题,其实感觉有点像穷举,但是不满足的提前被pass掉了。


三皇后问题
代码实现(golang 版本)
type EightQueue struct {
    Column []int
}

func (eq *EightQueue) CalEightQueues(row int) {
    if row == 8 {   // 行数满了
        fmt.Println(eq.Column)  // 打印 当前每行的Q都应该在哪一列
        eq.PrintQueue()         // 打印图
        return
    }
    for column := 0; column < 8;column ++ {  // 每一行有8种摆放方法
        if eq.IsOk(row,column) {      // 将当前 行,列 代入,查看之前已经放入的Q还是否满足8皇后条件
            eq.Column[row] = column
            eq.CalEightQueues(row+1)  // 考察下一行
        }
    }
}

func (eq *EightQueue) IsOk(row,column int) bool {   // 判断摆放位置是否合适
    leftup  := column - 1 // 左斜线上方(因为下方还没放入呢,所以不需要判断)
    rightup := column + 1 // 右斜线上方

    for i := row -1;i >= 0;i-- {  // 从当前行往第0行遍历
        if eq.Column[i] == column {     // 判断该列在某行(下标) 是否存在Q
            return false
        }
        if leftup >= 0 {
            if eq.Column[i] == leftup { // 判断其左上对角线上 是否存在Q
                return false
            }
        }
        if rightup < 8 {
            if eq.Column[i] == rightup { // 判断其右上对角线上 是否存在Q
                return false
            }
        }
        leftup--   // 左列 - 1
        rightup++  // 右列 + 1
    }
    return true
}

func (eq *EightQueue) PrintQueue() {

    for row := 0; row < 8 ;row++ {
        for column := 0; column < 8; column++ {
            if eq.Column[row] == column {
                fmt.Printf(" Q ")
            }else {
                fmt.Printf(" * ")
            }
        }
        fmt.Printf("\n")
    }
}

执行一下结果

func main() {
    eq := EightQueue.EightQueue{
        Column : make([]int ,8 ),
    }
    eq.CalEightQueues(0)  // 调用时,当然是从第0行开始遍历了
}

执行结果:
[0 4 7 5 2 6 1 3]
 Q  *  *  *  *  *  *  * 
 *  *  *  *  Q  *  *  * 
 *  *  *  *  *  *  *  Q 
 *  *  *  *  *  Q  *  * 
 *  *  Q  *  *  *  *  * 
 *  *  *  *  *  *  Q  * 
 *  Q  *  *  *  *  *  * 
 *  *  *  Q  *  *  *  * 
[0 5 7 2 6 3 1 4]
 Q  *  *  *  *  *  *  * 
 *  *  *  *  *  Q  *  * 
 *  *  *  *  *  *  *  Q 
 *  *  Q  *  *  *  *  * 
 *  *  *  *  *  *  Q  * 
 *  *  *  Q  *  *  *  * 
 *  Q  *  *  *  *  *  * 
 *  *  *  *  Q  *  *  * 
... 太多了,只打印这么点就够了

0-1 背包

0-1 背包的经典解法是动态规划,但是这里还是以回溯来说。

  • 问题:
    假设有一个背包,背包的总承载重量是 w kg。现在假设有 n 个物品,每个物品的重量不等,并且不可分割,现在期望选择几个物品,装载到背包中,在不超过背包所能装载重量的前提下,实现背包中的总重量最大。
  • 分析:
    对于每个物品来说,都有两种不同的选择,装进背包或者不装进背包。对于n个物品来说,总的装法就 2^n 种。去掉总重量超过 w kg的。从剩下的装法中选择重量最接近w kg的。不过,如何才能不重复的穷举出这 2^n 种装法呢。

有没有发现,这和八皇后问题十分相似,就是先穷尽,并及时pass掉不符合规定的。

  • 图解:


    背包问题

代码实现 (golang 版本)

type Knapsack struct {
    MaxWeight int    // 背包的最大承重
    Items     []int        // 待放入背包中的物品
}

type ItemInKnapsack struct {
    itemIndex  int    // 记录放进背包中的物品的下标
    itemWeight int  // 记录被放进背包中物品的重量
}

func (k *Knapsack) PutItemInKnapsack(CurWeight int,n int,items []ItemInKnapsack) {
    if n >= len(k.Items) {    // 表示 items 中的内容被遍历完了
        fmt.Println("共存放的物品个数",len(items))    // 统计一下装进背包里的item
        fmt.Printf("存放的为")
        sum := 0
        for _,v := range items {
            sum += v.itemWeight
            fmt.Printf(" 下标:%d 重量:%d ",v.itemIndex,v.itemWeight)
        }
        fmt.Printf("\n总重量为%d:",sum)
        fmt.Printf("\n-----\n")
        return
    }

    k.PutItemInKnapsack(CurWeight,n+1,items)    // 第n个物品我不放

    if CurWeight + k.Items[n] <= k.MaxWeight {  // 第n个物品,如果放进去还没达到最大重量,我就放
        items = append(items,ItemInKnapsack{n,k.Items[n]})
        k.PutItemInKnapsack(CurWeight + k.Items[n],n+1,items)
    }
}

执行一下

func main() {
       // 最大承重 10,items 是各个物品
    k := Knapsack.Knapsack{MaxWeight:10,Items:[]int{3,2,5,2,1,2,3,1,4,5,5,10}}
    k.PutItemInKnapsack(0,0,make(map[int]int,10))  // 当前重0,从第0个开始遍历
}

执行结果:
共存放的物品个数 0
存放的为
总重量为0:
-----
共存放的物品个数 1
存放的为 下标:11 重量:10 
总重量为10:
-----
共存放的物品个数 1
存放的为 下标:10 重量:5 
总重量为5:
-----
共存放的物品个数 2
存放的为 下标:9 重量:5  下标:10 重量:5 
总重量为10:
-----
共存放的物品个数 1
存放的为 下标:8 重量:4 
总重量为4:
...
共存放的物品个数 3
存放的为 下标:7 重量:1  下标:8 重量:4  下标:10 重量:5 
总重量为10:
-----
共存放的物品个数 3
存放的为 下标:7 重量:1  下标:8 重量:4  下标:9 重量:5 
总重量为10:
-----
共存放的物品个数 1
存放的为 下标:6 重量:3 
总重量为3:
-----

... 太多了,不全打印了

小结

回溯是什么,说白了,就是穷举,但是这个穷举,会在过程中就淘汰掉不符合规范的组合。

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

推荐阅读更多精彩内容

  • 微信公众号:小白算法关注可了解更多算法,并能领取免费资料。问题或建议,请公众号留言;文末有资料领取上一期算法回顾-...
    小白CV阅读 47,720评论 5 37
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,609评论 0 89
  • 一、回溯算法: 1、核心思想:   采用试错的思想,尝试分步的去解决一个问题。在分步解决问题的过程中,当通过尝试发...
    墨殇染泪阅读 361评论 0 1
  • 标准迭代范式 [回溯算法] 五大常用算法之回溯法 本文转自2018年02月12日 算法入门6:回溯法 一. 回溯法...
    高大宽333阅读 3,024评论 0 0
  • 基本思想 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,...
    木子中瑜阅读 699评论 0 50