暴力求解法--枚举排列

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
.
输出:
123 132 231 312 321
如果改成 while(next_permutation(a,a+2)); 则输出: 123 213
.
4.3 解答树
• 假设n=4,序列为{1,2,3,4},如图7-1所示的树显示出了递归函数的调用 过程。其中,结点内部的序列表示为A,位置cur用高亮表示,另外,由 于从该处开始的元素和算法无关,因此用星号表示。
• 从图7-1可以看出,它是一棵二叉树。第0层(根)结点有n个儿子,第1 层结点各有n-1个儿子,第2层结点各有n-2个儿子,第3层结点各n-3个儿 子,…,第n层结点都没有儿子(即都是叶子),而每个叶子对应于一 个排列,共有n!个叶子。由于这棵树展示的是逐步生成完整解的过程, 因此将其称为解答树。
• 但是上述递归函数print_permutation中,禁止A数组中出现重复,而在P 中可能就有重复元素时,所以输出数组A时就会出现问题。 解决方法是统计A[0]~A[cur-1]中P[i]的出现次数c1,以及P数组中P[i]的 出现次数c2。只要c1<c2,就能递归调用。
• 枚举的下标i应不重复、不遗漏地取遍所有P[i]值。由于P数组已经排过 序,所以只需检查P的第一个元素和所有“与前一个元素不相同”的元 素,即只需在for(i=0;i<n;i++)和其后的花括号之前加上if(!i||P[i]!=P[i-1]) 即可。
所以在设计递归函数需要以下参数: (1)已经确定的“前缀”序列,以便输出; (2)需要进行全排列的元素集合,以便依次选做第一个元素。 • 这样,写出以下的伪代码: • void print_permutation(序列A,集合S){
if(S为空) 输出序列A; else 按照从小到大顺序依次考虑S的每个元素v{
int a[3]; a[0]=1;a[1]=2;a[2]=3; do { cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl; } while (next_permutation(a,a+3)); //参数3指的是要进行排列的长 度 //如果存在a之后的排列,就返回true。如果a是最后一个排列没有后继 ,返回false,每执行一次,a就变成它的后继 }
• 为了推导方便,把n*(n-1)*(n-2)*…*(n-k)写成n!/(n-k-1)!,则所有结点之 和为:
• 根据高等数学中的泰勒展开公式, ,因此T(n)<n!·e=O(n!)。由于叶子有 n!个,倒数第二层也有n!个结点,因此上面的各层全部来源于最后一两 层,所以上面的结点数可以忽略不计。
.
算法设计与分析
——计算机学院 李欣
.
第四章 暴力求解法--枚举排列
1
4.1生成1~n的排列
2
4.2生成可重集的排列
3
4.3 解答树
4
4.4下一个排列
.
枚举排列
输入整数n,按字典序从大到小的顺序输出前n个数的所有排列。两个 序列的字典序大小关系等价于从头开始第一个不相同位置处的大小关系 。例如,(1,3,2)<(2,1,3),字典序最小的排列是(1,2,3,4,…,n),最大的排列 是(n,n-1,n-2,…,1)。n=3时,所有排列的排序结果是:(1,2,3)、(1,3,2)、
(2,1,3)、(2,3,1)、(3,1,2)、(3,2,1)
.
4.1生成1~n的排列
对此问题用递归的思想解决:先输出所有以1开头的排列(递归调用) ,然后输出以2开头的排列(递归调用),接着以3开头的排列,…,最后 才是以n开头的排列。 • 以1开头的排列的特点是:第一位是1,后面是按字典序的2~9的排列。
print_permutation(在A的末尾填加v后得到的新序列,S-{v}); } }
.
4.1生成1~n的排列
• 上面的递归函数中递归边界是S为空的情形,可以直接输出序列A;若S 不为空,则按从小到大的顺序考虑S中的每个元素,每次递归调用以A 开头。
• 下面考虑程序的实现。用数组表示序列A,集合S可以由序列A完全确定 ——A中没有出现的元素都可以选。C语言中的函数在接受数组参数时 无法得到数组的元素个数,所以需要传一个已经填好的位置个数,或者 当前需要确定的元素位置cur。声明一个足够大的数组A,然后调用 print_permutation(n,A,0),即可按字典序输出1~n的所有排列。
.
4.2 生成可重集的排列
• 如果把问题改成: 输入数组A,并按字典序输出数组A各元素的所有全排列,则需要对上 述递归函数print_permutation修改——把P加到print_permutation的参数列 表中,然后把代码中的if(A[j]==i)和A[cur]=i分别改成if(A[j]==P[i])和 A[cur]=P[i]。只有把P的所有元素按从小到大的顺序排序,然后调用 print_permutation(n,P,A,0)即可。
.
4.3 解答树
• :如果某问题的解可以由多个步骤得到,而每个步骤都有若干种选择( 这些候选方案集可能会依赖于先前作出的选择),且可以用递归枚举法 实现,则它的工作方式可以用解答树描述。
• 通过逐层查看,第0层有1个结点,第1层有n个结点,第2层有n*(n-1)个 结点,第3层有n*(n-1)*(n-2)个结点,…,第n层有n*(n-1)*(n2)*…*2*1=n!个。
4.4 下一个排列
枚举所有排列的另一个方法是从字典序最小排列开始,不停调用“求 下一个排列”的过程,在C++的STL中提供了一个库函数next_permutation 。与之完全相反的函数还有prev_permutation. (1) int 类型的next_permutation
int main() {
• 在递归函数print_permutation中循环变量i是当前考虑的A[cur]。为了检查 元素i是否已经用过,还用到了一个标志变量ok,初始值为1(真),如 果发现有某个A[j]==i时,则改为0(假)。如果最终ok仍为1,则说明i 没有在序列中出现过,把它添加到序列末尾(A[cur]=i)后递归调用。
相关文档
最新文档