以"求序列的全排列"问题加深对回溯法的理解

近几天回顾基础时,对于试探、回溯法求解的过程,虽感性上能体会,但无法真正说服自己,貌似懂了其实是没懂。查找资料后,虽然能获得严谨深刻的剖析,但对我来说仍感不够明著。下面尝试以“求一个集合中元素的全排列”为例,通过尽量详细的图文推导、辅以少量理论描述,以期对试探、回溯问题培养更清晰的直觉。(私以为:思绪若不形成图文,则很不清晰;若不加以实践,则可能不靠谱。)

1.回溯法简介

现实世界中,有一类求一组解、全部解或最优解的问题,往往不容易找到确定的计算法则①,求解的直观思路,是有条理地穷举、试探、回溯,以获得全部解或最优解。比如“求一个集合的所有子集”,又如“四皇后问题(八皇后问题的简化)”②。但当输入数据的规模较大时,人力显难为。若把此过程交由计算机程序,则需把对机器而言抽象的“穷举、试探、回溯”操作具体化。计算机求解问题的思路,是在现实经验的基础上进一步抽象,若把我们手动穷举的经验加以总结,会发现,回溯法求解问题的过程实质上是遍历一颗状态树,建立这颗状态树的规则,可用机器推理领域常用的一个术语——产生式集合③——概括。不过,这棵树并不是预先建立好的,而是隐含在遍历过程中,根据需要,既可动态建立(过程类似二叉搜索树的建立)这棵树,也可只当作一种分析方式。下面通过解析“一个集合中元素的全排列”问题,以求具体说明一二。

2.字符串的全排列

简便起见,把“一个集合中元素的全排列”中的“集合”具体化为字符集合,即字符串。

问题描述: 输入一个字符串,输出其所有可能的排列方式(全排列)。比如输入ABC,输出ABC、ACB、BAC、BCA、CAB、CBA。

用短字符串在纸上划拉,而后总结手动解法,发现每求一种排列,纯由下述操作完成:

  • 1.固定一个字符,而后把注意力放在除此之外的子序列,对其也进行全排列,完成后,把子序列的全排列依次和固定的字符合并,得到部分解。更换固定字符,重复上述操作直到所有字符均经历固定,此时得到全部解;
  • 2.对于除固定字符外的子序列,重复第1项所描述的操作;
  • 3.若子序列的长度退化至1,则把它与之前固定的所有字符合并,得到一种排列,而后回溯至上层状态(子序列长度为2的状态),更换当前子序列的固定字符,如果当前子序列所有字符均经历固定,则回溯至前驱状态(子序列长度为3的状态),依次类推,可知最后将回退至子序列长度为n-1的状态(即固定字符序列的长度为1的状态);

可见,该过程先是层层递进,而后步步回溯,即先递后归。另外,上述3个步骤,均基于一种基本操作:固定子序列的某个元素。若把每一次这样的操作所得到的子序列看成一种状态,可建立一颗状态树,整个问题的求解,则转为遍历此(多叉)树、求所有叶子节点的过程,直观起见,可以字符串“ABCD”为例,画出下图。(由于空间限制,只标出了部分状态,不失一般性)

Tips: 实践上,“依次固定某个子序列的元素”,等价于把首元素分别与子序列中的所有元素交换(包括首元素本身),如此,形式上可获得更加简单统一的算法描述。

这里写图片描述

有了图文分析,容易得出伪代码如下:

// 求集合sequence元素之间的全排列;
void permutation(ElementType sequence, int start, int end) {
    if (start == end) 
        // 当前子序列长度为1, 意味着此时的sequence即为一个排列.
        保存当前排列;
        输出当前排列;
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值