`
varsoft
  • 浏览: 2429879 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

排列组合算法1:生成全部有序列b

阅读更多
对推导不感兴趣的老大们可以通过搜索”def”直接跳到代码实现部分。不过有闲心还是瞧瞧推导过程的好。我们可以看见好的算法并非无迹可寻,完全依赖某位大牛的灵光闪现,而是通过观察、归纳、试验、迭代改进,逐步雕琢而成。另外,我们时常感叹,要是有时间学习算法就好了。要是有时间仔细读读TAOCP就好了。其实呢,读TAOCP也不是抢鸡蛋的大事。想读了,就备好纸笔,调出趁手的编辑器,打开书,翻到自己感兴趣的章节。遇到公式就推一下。遇到算法就实现 一下。遇到概念就举几个例子印证一下。遇到难解之处就和Google勾兑一下(不要忘了Google Group,真正的技术宝藏)。至于一次读多久,钻多深,施主随喜。再说从俺写的这个系列可以看出,TAOCP不少章节也就需要高中知识,到数列为止。本来是很美好的事。不必正襟危坐。不必如临大敌。不必把自己逼得太紧。你会发现花的时间不多,收获却不小。
上次说到利用递归定义生成格雷码。为了方便,我们把公式重新列一下:
其实我们也可以用递推公式定义格雷码:,明确地给出格雷码表里每一位格雷码的表达式: 。这里的g(k)表示在格雷码表里排在第k位的格雷码。其实,因为 开头,格雷码的无限序列是所有非负整数的某个排列:
如果我们把每个格雷码看作前面可以填充零的二进制整数,那长度为N的格雷码序列 包含公式(4)中最前面的2n个整数。只不过每个整数前可能填充0,使之长度为N。再把整数转换成字符串,就生成了相应的格雷码。比如说当N=3时,第二位格雷码是001,相当于在公式(4)里的第二个整数(1)2前面补上两个0后将其转换为字符串,使得该字符串的长度等于3。
当k = 2n + r 且 时,我们可以从公式(3)得出g(k) = 2n + g(2n – 1 – r )。这个结论的推导也挺容易:既然k > 2n, 那么我们知道g(k)一定在 里面。既然 表示将 反转,那 中对应第r位的格雷码刚和等于 中第2n-r-1位的格雷码。最后,在g(r)前添一个1,正好相当于g(r)+(1000…000)2,共n个0。换成10进制,就得到g(k) = 2n + g(2n – 1 – r )了。根据这个公式,我们可以得出一个非常有用的结论:给出整数k和它的二进制形式(…b2b1b0)2,以及第k位的格雷码g(k)的二进制形式(…a2a1a0)2,我们可以用数学归纳法证明下面的关系:
这个 表示位运算中的异或运算。也就是说,只有当两个位不一样时他们异或的结果为1。不然就为0。比如说,当k = 3651时,它的二进制形式是(111001000011)2。那第k位的格雷码g(k)就是 k ^ (k >> 1) = 2402 = (100101100010)2。证明其实也不难,利用公式g(k) = 2n + g(2n – 1 – r )对n做归纳就行。有兴趣的老大们可以自己去做。网上也有答案。
现在我们把 从j开始展开,看看能得到什么:
对等式两边分别求异或,我们得到
中间项都消掉了,因为 等于0, 而0在异或运算中是identity, 也就是说任何一个值与0异或还是等于自身。这个等式看来是无穷项,但因为 迟早会等于0, 所以它其实是有限的,可以计算。
根据公式(5),我们得到很酷的公式:
注意这个 对应编程里的位移运算:k >> 1,所以实现起来也方便。我们立刻有了新的算法,异常简洁:

defgray_g(n)
return [] if n ==0
return (0..((1<<n) -1)).collect{|k|sprintf(“%0#{n}b”, (k ^ (k>>1)))}
end
上述程序无非是说,从0循环到2n, 对每个循环k, g(k) = k ^ (k >> 1)。够简洁吧?唯一的问题是,这个方法还是不够高效。每次循环,我们都要对k做位移。而位移的时间和k的长度成正比。循环2n次是很大的开销,我们自然希望每次循环的计算量越小越好。
同理,公式(6)可以导出很酷的反向公式。于是我们可以求出任意格雷码的位置:
如果有些老大对这种写法不习惯,下面的等价写法也许清楚点:
对应的代码也很直观:
def gray_pos(gray_code)
pos = g = gray_code.to_i(2)
while g > 0
g >>= 1
pos ^= g
end



return pos
end
其实格雷码的原理早已隐含在九连环的解法里。九连环的传统解法对应一种颇为高效的格雷码生成代码。关于九连环的知识,可以到这里或者这里去了解。俺就不罗嗦了。从推导算法的角度出发,我们只需要知道玩儿九连环的两条规则(引自上面链接的文章):
a): “第1号环随时可自由上下”
b): “其他环当且仅当它前面仅有与它相邻的一个环在上面时可自由上下。”
(从TAOCP截的图)
基于这两条规则,我们可以把九连环上每个环的状态用2进制数表示。环在杠上,对应的数字为1,环在杠下,对应的数字为0。比如上图的,从左向右数,对应的数字是(1011000)2。注意第二个环没有套在杠上。这样的话,解环对应于把(111…11)2变成(000…00)2。而套环对应于把(000…00)2变成(111…11)2。因为每次最多变动一个环,解环或套环环的过程相当于生成格雷码全序列。
1872年一个名叫Louis Gros的法国官员证明了如果一个九连环的状态为(an-1…a0)2, 而我们按上面的公式(6)定义一个二进制数k = (bn-1, … , b0)2, 那么我们可以刚好用k步解除该九连环。从这个意义说,Gros老大才是格雷码的真正发明人。
我们用解环来说明格雷码的生成过程。只要九连环的状态不是(000…00)2或(100…00)2, 那我们只可能执行规则a)或者b),而且只有其中一步可以让我们靠近答案。规则a)把环取下时,相当于最末一环的状态从1变成0, 而套环相当于把0变成1。也就是说 。参考公式(6),这相当于把把k变成 ,因为只有k0变了。显然我们希望当k的最后一位是1,也就是当k为奇数时按规则a)执行。这样k变成k-1,离我们的目标近了一步。另外一方面,根据规则b),只有形为(…x100…00)2的九连环可以移动x代表的那个环。换句话说,如果一个长度为n的九连环里某个环所在位置以(10j-1)2结尾(这里(10j-1)2表示一个二进制数,第一位是1,后面跟了j – 1个0), 1 <= j < n, 则该环可以移动。该环对应aj+1, 所以移动后该环的状态变成 。根据公式(6),我们知道bj, bj-1,… , b0, 都包含aj+1项。所以说移动该环后,k变成了 。举个例子哈。上面九连环图里从右数第3个环可以被解下。用数字来表达,就是(1011000)2可以变成(1001000)2。具体到右数第三左数第5个环,我们可以说a4从1变成0。解环前k等于gray_pos(0b101100) = 55=(110111)2, 解下环后k变成了 = gray_pos(0b100100) = 56 = (111000)2。同执行规则a)一样,我们希望执行规则b)后k变小: 应该等于k – 1。要达到这个目标,k必须是2j的倍数,但不能是2j+1的倍数。这是因为形如(xxx…10j)2的数才是2j的倍数。它和2j+1-1异或后,才能等于(xxx…01j )2,也就是k-1。我们可以把j和k的关系表达为:
这里的函数 叫作ruler function,有兴趣的老大们可以参见这里的第13页,印张第8页,公式32到45。那节专讲位运算的技巧,可以和IBM某编译器牛人出的Hacker's Delight以及这个网页一块儿看。做嵌入式的老大和雅好底层优化的老大们应该可以获益不少。不过这里俺就不跑题了。我们只需要知道 返回k靠右连续0的个数。比如说 。从九连环可以自由移动的那头给环的移动次序编号:0, 1, 2, …, , 那把环的状态从00000…0变成1111…11的移动顺序就是 。那对应的算法也就很直观了:设定长度为n的序列,该算法从(0, 0, 0…, 0)开始,逐次访问每个组合:(an-1, …, a0)2。每一步只改变一位,相当于移动一个环。对每一步i, 移动对应的环相当于找到 对应的环,然后对它的值求补(异或)。我们也不需要每次通过求异或判断奇偶来决定执行规则a)还是规则b):维护一个奇偶值,parity就行了:
32: def alg_g(n)
33: step = Array.new(n, 0)
34: result = []
35: parity = 0
36:
37: loop do
38: result << step.join
39: parity = 1 - parity
40:
41: if parity == 1
42: j = 0
43: else
44: 0.upto(n-1) do |i|
45: if step[i] == 1
46: j = i+1
47: break
48: end
49: end
50: end
51:
52: return result.reverse if j == n
53:
54: step[j] = 1 - step[j]
55: end
56: end
这个奇偶位很有点意思。当我们要计算求和公式
或者
且计算结果只依赖于二进制字串的奇偶性或者某个子集里的元素个数的时候,这个公式就变得很方便了。而且这个奇偶位也让上面的算法变得高效:没有它的话,决定到底执行规则a)还是规则b)就不容易了。
这个算法最精当的地方在于第54行。我们只需要对一个bit做出改动。不过呢,对每一个生成的格雷码,我们有可能执行第44到48行的内循环。当我们要执行2n次外循环时,这个开销还是大了点。所以我们接下来要介绍除去Gideon Ehrlich在1973年提出的方法,消去内循环,并从中学到新的设计算法解决问题的手段。
分享到:
评论
1 楼 nothingtalk 2011-02-27  
博主,您好。
“所以我们接下来要介绍除去Gideon Ehrlich在1973年提出的方法,消去内循环,并从中学到新的设计算法解决问题的手段。

说的文章怎么找不到呢?不会是册了吧?

相关推荐

    一种基于倒置序列的排列生成算法

    一种基于倒置序列的排列生成算法论文。有详细的算法介绍信息

    高效算法:竞赛、应试与提高必修128例.[法] Christoph Dürr Jill-Jênn Vie(带书签文字版).pdf

    本书旨在探讨如何优化算法效率,详细阐述了经典算法和特殊算法的实现、应用技巧和复杂度验证过程,内容由浅入深,能帮助读者快速掌握复杂度适当、正确率高的高效编程方法以及自检、自测技巧,是参加ACM ICPC、Google...

    组合数学(第4版) 卢开澄 卢华明

    本书是《组合数学》第3版的修订版,全书共分8章,分别是:排列与组合、递推关系与母函数、容斥原理与鸽巢原理、burnside引理与polya定理、区组设计、线性规划、编码简介、组合算法简介。丰富的实例及理论和实际相...

    Java 模拟操作系统页面替换算法

    Java图形化界面实现以下要求,我上传给大家一同分享。 通过随机数产生一个指令序列,共 320 条指令,指令的地址按下述原则生成: ...5、分别采用 FIFO 和 LRU 算法对页号序列进行调度,并计算出对应的缺页中断率。

    大序列的算法伪随机排列:permdata = createRandomPermutation(numobjects, nrounds) 创建一个伪随机排列-matlab开发

    % PERMDATA = createRandomPermutation(NUMOBJECTS, NROUNDS) 生成一个%struct表示序列的伪随机排列% 1:NUMOBJECTS。 % % 排列是通过算法生成的,因此无需% 保存在内存中。 这对于伪随机排列很有用% 太大,无法使用...

    ACM算法模板和pku代码

    r-错位排列生成算法 图论 传递闭包 欧拉回路判定 有向图欧拉路径 二分图最大匹配 匈牙利算法 二分图最大匹配 HK算法 二分图最大权匹配 KM算法 割边 强连通分量 缩点 Kosaraju算法 最大团 最小树形图 无...

    计算机算法设计与分析试题.doc

    A 单源最短路径问题 B N皇后问题 C 最小花费生成树问题 D 背包问题 31、下列算法中不能解决0/1背包问题的是(A ) A 贪心法 B 动态规划 C 回溯法 D 分支限界法 32、回溯法搜索状态空间树是按照(C )的顺序。...

    算法设计与分析王晓东

    第1章 算法引论 1.1 算法与程序 1.2 表达算法的抽象机制 1.3 描述算法 1.4 算法复杂性分析 小结 习题 第2章 递归与分治策略 2.1 速归的概念 2.2 分治法的基本思想 2.3 二分搜索技术 2.4 大整数的乘法 2.5 Strassen...

    一种用于图像拼接的图像序列自动排序算法

    对碎纸片的拼接与复原绝对有用的,好好珍惜啊,祝你取得好成绩!

    ACM 算法经典代码 数据结构经典代码

    1. 排列组合生成 59 2. 生成gray码 60 3. 置换(polya) 61 4. 字典序全排列 61 5. 字典序组合 62 6. 组合公式 62 十. 数值计算 63 1. 定积分计算(Romberg) 63 2. 多项式求根(牛顿法) 64 3. 周期性方程(追赶法) 66 ...

    acm常用代码及算法

    1.Prim算法求最小生成树 2.Dijkstra算法求单源最短路径 3.Bellman-ford算法求单源最短路径 4.Floyd算法求每对节点间最短路径 排序/查找: 1.快速排序 2.希尔排序 3.选择法排序 4.二分查找...

    内存管理算法

    1) 最佳置换算法(Optimal) 2) 先进先出法(Fisrt In First Out) 3) 最近最久未使用(Least Recently Used) 4) 最不经常使用法(Least Frequently Used) 5) 最近未使用法(No Used Recently) 其中,命中率=1...

    常用算法代码

    | RMQ 离线算法 O(N*LOGN)+O(1)求解 LCA 19 | LCA 离线算法 O(E)+O(1) 20 | 带权值的并查集 20 | 快速排序 20 | 2 台机器工作调度 20 | 比较高效的大数 20 | 普通的大数运算 21 | 最长公共递增子序列 O(N^2) ...

    数据结构实验

    实验1:顺序表基本操作 一、实验目的 1.学会定义线性表的顺序存储类型,实现C程序的基本结构,对线性表的一些基本操作和具体的函数定义。 2.掌握顺序表的基本操作,实现顺序表的插入、删除、查找以及求并集等运算。 ...

    基于A*算法的人工智能程序

    每当扩展结点时,意是在所有待扩展结点中选择具有最小F值的结点做为扩展对象,以便使搜索尽量沿最有希望的方向进行.A*算法只要求产生问题的全部状态空间的部分结点及关系,就可以求解问题了,搜索效率较高。 1.3 A*算法...

    Gray码的分治构造算法

    Gray码是一个长度为2ⁿ的序列,序列中无相同元素,且每个元素都是长度为n位的二进制位串,相邻元素恰好只有1位不同。例如长度为2³的格雷码为(000,001,011,010,110,111,101,100),设计分治算法对任意的n值构造相应...

    ACM算法竞赛常用代码

    组合数学(排列与组合,鸽笼原理,容斥原理,递推,Fibonacci数列,Catalan数列,Stirling数,差分序列,生成函数,置换,Polya原理) 概率论(简单概率,条件概率,Bayes定理,期望值) 矩阵(矩阵的概念和运算...

    算法分析与设计习题集答案

    22、 对于上图给出的有向图,写出最小成本生成树,给出求解算法。 动态规划 23、 求出上图中每对结点间的最短距离的算法,并给出计算结果。 24、 下图中给出了一个地图,地图中每个顶点代表一个城市,两个城市间的...

    Sequency (Walsh) 有序 Hadamard 矩阵:生成大小为 N 的序列 (Walsh) 有序 Hadamard 矩阵。-matlab开发

    此函数生成大小为 N 的序列 (Walsh) 有序 Hadamard 矩阵。 之前提交的 m 文件已经实现了这一点,但这是一个更快的算法。

    algorithm:算法学习~

    记录我在各个网站的算法刷题题解~ 2018/5/23更新 蓝桥杯第九届省赛题目 2018/5/23-26蓝桥杯复习规划 图论 求最短路 宽搜 迪杰斯特拉 弗洛伊德 求最小生成树 prims 克鲁斯卡尔 求联通块-割点-割边 树 ...

Global site tag (gtag.js) - Google Analytics