目录
[1.在你的计算机上实现一个要求生成 25 个元素组成的集合的全部排列的算法是否现实?如果是生成该集合的所有子集呢?](#1.在你的计算机上实现一个要求生成 25 个元素组成的集合的全部排列的算法是否现实?如果是生成该集合的所有子集呢?)
[b. Johnson-Trotter算法。](#b. Johnson-Trotter算法。)
[3. 把 LexicographicPermute算法应用到多重集{1,2,2,3}上。它是否能正确生成字典序的所有排列?](#3. 把 LexicographicPermute算法应用到多重集{1,2,2,3}上。它是否能正确生成字典序的所有排列?)
[4.请考虑下面这个生成排列算法的实现,这个算法是由 B.希普(B. Heap)发明的([Hea63])。](#4.请考虑下面这个生成排列算法的实现,这个算法是由 B.希普(B. Heap)发明的([Hea63])。)
[b.证明 Heap算法的正确性。](#b.证明 Heap算法的正确性。)
[c. HeapPermute的时间效率如何?](#c. HeapPermute的时间效率如何?)
5.用本节介绍的两种算法,分别对一个4元素的集合A=(a₁,a₂,a₃,a₄)生成它的所有子集。
6.有什么简单的小窍门可以使得基于位串的算法可以按照挤压序生成子集?
7.有一个生成所有2ⁿ个长度为n的位串的递归算法,为它编写伪代码。
8.写一个生成2ⁿ个长度为n的位串的非递归算法,它用数组来实现位串并且不使用二进制加法。
[b.跟踪下面生成4位二进制反射格雷码的非递归算法。以全0的n位串开始。而对于 i=1,2,...,2ⁿ⁻¹,则通过反转前一位串中的第b位来生成第i个位串,在这里b是i的二进制表示中最低位1的位置。](#b.跟踪下面生成4位二进制反射格雷码的非递归算法。以全0的n位串开始。而对于 i=1,2,…,2ⁿ⁻¹,则通过反转前一位串中的第b位来生成第i个位串,在这里b是i的二进制表示中最低位1的位置。)
10.设计一个减治算法来生成n个元素的k个分量的所有组合,也就是说,一个给定的n元素集合的所有k元素子集。你设计的算法是最小变化算法吗?
a.为什么汉诺塔的经典递归算法产生的移动盘子动作可以用来生成二进制反射格雷码?
[12.展会彩灯 早些年,在展会上可能会看到这样一种彩灯:一个被连接到若干开关上的电灯泡,只当所有开关都闭合的时候才会发光。每一个开关由一个按钮控制;按下按钮就会切换开关状态,但是开关的状态是无法知道的。目标就是点亮灯泡。设计一个点亮灯泡的算法,使其在有n个开关时,在最坏的情况下,需要按动按钮的次数最少。](#12.展会彩灯 早些年,在展会上可能会看到这样一种彩灯:一个被连接到若干开关上的电灯泡,只当所有开关都闭合的时候才会发光。每一个开关由一个按钮控制;按下按钮就会切换开关状态,但是开关的状态是无法知道的。目标就是点亮灯泡。设计一个点亮灯泡的算法,使其在有n个开关时,在最坏的情况下,需要按动按钮的次数最少。)
1.在你的计算机上实现一个要求生成 25 个元素组成的集合的全部排列的算法是否现实?如果是生成该集合的所有子集呢?
由于 25!≈1.5·10^25,即使在超级计算机上生成这么多排列也需要不切实际的长时间。另一方面,2^25≈3.3·10^7,在每秒进行一亿次操作的计算机上生成大约需要 0.3 秒。
2.使用下面的方法生成{1,2,3,4}的全部排列:
a.从底向上的最小变化算法。

b. Johnson-Trotter算法。
c.字典序算法。

3. 把 LexicographicPermute算法应用到多重集{1,2,2,3}上。它是否能正确生成字典序的所有排列?
1223,1232,1322,2123,2132,2213,2231,2312,2321,3122,3212,3221
4.请考虑下面这个生成排列算法的实现,这个算法是由 B.希普(B. Heap)发明的([Hea63])。
算法 HeapPermute(n)
//实现生成排列的 Heap 算法
//输入:一个正整数n和一个全局数组A[1..n]
//输出: A中元素的全排列
if n=1
write A
else
for i←1 to n do
HeapPermute(n-1)
if n is odd
swap A[1] and A[n]
else swap A[i] and A[n]
a.对于n=2,3,4的情况,手工跟踪该算法。
n=2时:
HeapPermute(2)
i=1:
HeapPermute(1) → 输出 [1,2]
n 偶数 → swap A[1] ↔ A[2] → A=[2,1]
循环结束
输出:
1 2
2 1
n=3 时:
HeapPermute(3)
i=1:
HeapPermute(2) → 输出 123, 213
n 奇数 → swap A[1] ↔ A[3] → A=[3,1,2]
i=2:
HeapPermute(2) → 输出 312, 132
n 奇数 → swap A[1] ↔ A[3] → A=[2,1,3]
i=3:
HeapPermute(2) → 输出 213, 123
n 奇数 → swap A[1] ↔ A[3] → A=[3,1,2]
b.证明 Heap算法的正确性。
-
基例 n=1直接输出数组,唯一排列,成立。
-
归纳假设 假设
HeapPermute(k)能正确生成 k 个元素的全部 k! 个排列。 -
归纳步骤 n = k+1
- 循环执行 n 次,每次调用
HeapPermute(n-1),生成前 n-1 个元素的全部排列。 - n 偶数 :交换
A[i] ↔ A[n],把第 n 个位置换上新元素。 - n 奇数 :交换
A[1] ↔ A[n],固定轮换最后一位。 - 每次调用都生成完整的 (n-1)! 个排列,共 n 次 → 总排列数 n×(n-1)! = n!。
- 循环执行 n 次,每次调用
c. HeapPermute的时间效率如何?
时间效率:O (n!)
5.用本节介绍的两种算法,分别对一个4元素的集合A=(a₁,a₂,a₃,a₄)生成它的所有子集。
自底向上:
n=0: { }
n=1: { }, {a₁}
n=2: { }, {a₁}, {a₂}, {a₁,a₂}
n=3: { }, {a₁}, {a₂}, {a₁,a₂}, {a₃}, {a₁,a₃}, {a₂,a₃}, {a₁,a₂,a₃}
n=4: 在上面所有子集后面,依次添加 a₄
二进制:
0000 → { }
0001 → {a₁}
0010 → {a₂}
0011 → {a₁,a₂}
0100 → {a₃}
0101 → {a₁,a₃}
0110 → {a₂,a₃}
0111 → {a₁,a₂,a₃}
1000 → {a₄}
1001 → {a₁,a₄}
1010 → {a₂,a₄}
1011 → {a₁,a₂,a₄}
1100 → {a₃,a₄}
1101 → {a₁,a₃,a₄}
1110 → {a₂,a₃,a₄}
1111 → {a₁,a₂,a₃,a₄}
{ },
{a₁}, {a₂}, {a₁,a₂},
{a₃}, {a₁,a₃}, {a₂,a₃}, {a₁,a₂,a₃},
{a₄}, {a₁,a₄}, {a₂,a₄}, {a₁,a₂,a₄},
{a₃,a₄}, {a₁,a₃,a₄}, {a₂,a₃,a₄}, {a₁,a₂,a₃,a₄}
6.有什么简单的小窍门可以使得基于位串的算法可以按照挤压序生成子集?
把数字按照二进制 格雷码(Gray Code)的顺序生成,而不是普通的 0,1,2,3... 顺序
7.有一个生成所有2ⁿ个长度为n的位串的递归算法,为它编写伪代码。
算法:GenerateAllBits(n)
输入:正整数 n
输出:所有长度为 n 的二进制位串
// 辅助递归函数
过程 Backtrack(pos, current[])
if pos > n
输出 current[] // 生成完成,打印位串
return
// 当前位置放 0
current[pos] = 0
Backtrack(pos + 1, current)
// 当前位置放 1
current[pos] = 1
Backtrack(pos + 1, current)
// 主算法
主函数
定义数组 current[1..n]
Backtrack(1, current)
8.写一个生成2ⁿ个长度为n的位串的非递归算法,它用数组来实现位串并且不使用二进制加法。
初始化全 0,每次找最右边的 0 变为 1,其右侧全部置 0,循环直到生成全部 2ⁿ个串。不使用二进制加法。
算法:BitStringIterative(n)
// 非递归生成所有长度为n的二进制位串
// 不使用二进制加法,用数组实现
输入:正整数 n
输出:所有 2ⁿ 个长度为 n 的位串
A[1..n] ← {0} // 初始化全0数组
输出 A
for i ← 1 to 2ⁿ − 1 do
// 找最右边的0,把它变成1
j ← n
while j ≥ 1 and A[j] = 1 do
j ← j − 1
A[j] ← 1 // 最右0 → 1
// j 右边全部变回 0
for k ← j+1 to n do
A[k] ← 0
输出 A

9.
a.生成4位的二进制反射格雷码。
0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000
b.跟踪下面生成4位二进制反射格雷码的非递归算法。以全0的n位串开始。而对于 i=1,2,...,2ⁿ⁻¹,则通过反转前一位串中的第b位来生成第i个位串,在这里b是i的二进制表示中最低位1的位置。
同a一样
10.设计一个减治算法来生成n个元素的k个分量的所有组合,也就是说,一个给定的n元素集合的所有k元素子集。你设计的算法是最小变化算法吗?
全局数组:A[1..k] // 存放当前组合
主调用:Choose(1, k)
过程 Choose(i, k)
// 生成 {i, i+1, ..., n} 中所有 k 元子集
// 结果存在 A 里,按**降序**输出
if k == 0
输出 A
else
for j = i to n - k + 1 do
A[k] = j // 把 j 放在第 k 位(最后一位)
Choose(j + 1, k - 1)
不是最小变化算法,因为相邻组合可能变化多个元素,不满足相邻仅变化一个元素的条件
11.格雷码和汉诺塔
a.为什么汉诺塔的经典递归算法产生的移动盘子动作可以用来生成二进制反射格雷码?
因为汉诺塔每次只移动 1 个盘子,而格雷码相邻两个数也正好只变 1 位,它们的 "最小变化" 结构完全一样
b.如何利用二进制反射格雷码来解汉诺塔问题?
000 → 001 → 011 → 010 → 110 ...... 第1位变 第2位变 第1位变 第3位变......对应的汉诺塔:
移动1号盘 → 移动2号盘 → 移动1号盘 → 移动3号盘......
12.展会彩灯 早些年,在展会上可能会看到这样一种彩灯:一个被连接到若干开关上的电灯泡,只当所有开关都闭合的时候才会发光。每一个开关由一个按钮控制;按下按钮就会切换开关状态,但是开关的状态是无法知道的。目标就是点亮灯泡。设计一个点亮灯泡的算法,使其在有n个开关时,在最坏的情况下,需要按动按钮的次数最少。
按照 n 位二进制反射格雷码的顺序,依次切换每一步变化的那一位开关。
此时递推式为:

最坏情况只需要按 2ⁿ − 1 次
