全排列的生成算法就是对于给定的字符集,用有效的方法将所有可能的全排列无重复无遗漏地枚举出来。任何n个字符集的排列都可以与1~n的n个数字的排列一一对应,因此在此就以n个数字的排列为例说明排列的生成法。
n个字符的全体排列之间存在一个确定的线性顺序关系。所有的排列中除最后一个排列外,都有一个后继;除第一个排列外,都有一个前驱。每个排列的后继都可以从 它 的前驱经过最少的变化而得到,全排列的生成算法就是从第一个排列开始逐个生成所有的排列的方法。
全排列的生成法通常有以下几种:
字典序法
递增进位数制法
递减进位数制法
邻位交换法
n进位制法
递归类算法
1.字典序法
字典序法中,对于数字1、2、3......n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列 12354和12345,排列12345在前,排列12354在后。按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是 54321。
字典序算法如下:
设P是1~n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn
1)从排列的右端开始,找出第一个比右边数字小的数字的序号j(j从左端开始计算),即 j=max{i|pi
3)对换pi,pk
4)再将pj+1......pk-1pkpk+1pn倒转得到排列p'=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,这就是排列p的下一个下一个排列。
例如839647521是数字1~9的一个排列。从它生成下一个排列的步骤如下:
自右至左找出排列中第一个比右边数字小的数字4 839647521
在该数字后的数字中找出比4大的数中最小的一个5 839647521
将5与4交换 839657421
将7421倒转 839651247
所以839647521的下一个排列是839651247。
程序代码如下:
Private Sub Dict(p() As Integer, ByVal n As Integer)
Dim i As Integer, j As Integer
OutL p
i = n - 1
Do While i > 0
If p(i) < p(i + 1) Then
For j = n To i + 1 Step -1 '从排列右端开始
If p(i) <= p(j) Then Exit For '找出递减子序列
Next
Swap p(i), p(j) '将递减子序列前的数字与序列中比它大的第一个数交换
For j = n To 1 Step -1 '将这部分排列倒转
i = i + 1
If i >= j Then Exit For
Swap p(i), p(j)
Next
OutL p '输出一个排列
i = n
End If
i = i - 1
Loop
End Sub
Swap p(i), p(j)是交换两个元素的子过程,OutL p是输出排列的子过程。
2.递增进位数制法
在递增进位制数法中,从一个排列求另一个排列需要用到中介数。如果用 ki表示排列p1p2...pi...pn中元素pi的右边比pi小的数的个数,则排列的中介数就是对应的排列k1 ...... ki...... kn-1。
例如排列839647521的中介数是72642321,7、2、6、......分别是排列中数字8、3、9、......的右边比它小的数字个数。
中介数是计算排列的中间环节。已知一个排列,要求下一个排列,首先确定其中介数,一个排列的后继,其中介数是原排列中介数加1,需要注意的是,如果中介数 的末位kn-1+1=2,则要向前进位,一般情形,如果ki+1=n-i+1,则要进位,这就是所谓的递增进位制。例如排列839647521的中介数是 72642321,则下一个排列的中介数是67342221+1=67342300(因为1+1=2,所以向前进位,2+1=3,又发生进位,所以下一个 中介数是67342300)。
得到中介数后,可根据它还原对应得排列。算法如下:
中介数k1、k2、......、kn-1的各位数字顺序表示排列中的数字n、n-1、......、2在排列中距右端的的空位数,因此,要按k1、 k2、......、kn-1的值从右向左确定n、n-1、......、2的位置,并逐个放置在排列中:i放在右起的ki+1位,如果某位已放有数字, 则该位置不算在内,最后一个空位放1。
因此从67342300可得到排列849617523,它就是839647521的后一个排列。因为9最先放置,k1=6,9放在右起第7位,空出6个空位,然后是放8,k2=7,8放在右起第8位,但9占用一位,故8应放在右起第9位,余类推。
程序代码如下:
Private Sub Incr(p() As Integer, ByVal n As Integer)
Dim m() As Integer '保存中介数的数组
Dim i As Integer, j As Integer
Dim a As Integer
ReDim m(n)
For i = 1 To n '第一个排列的中介数为000......0
m(i) = 0
Next
Do While n > 0
For i = 1 To n '排列的各位为0
p(i) = 0
Next
For i = 1 To n '从右向左察看排列中为0的位
a = m(i) + 1
j = n
Do While j > 0
If p(j) = 0 Then
a = a - 1
If a = 0 Then Exit Do '0的个数决定数字i的位置
End If
j = j - 1
Loop
p(j) = n - i + 1 '将数字i放置在指定位置
Next
OutL p
If MedN(m) Then Exit Do '计算下一个中介数,如果是00...0,则全部排列找到
Loop
End Sub
Private Function MedN(m() As Integer)As Boolean '计算中介数函数
Dim i As Integer, sum As Integer
Dim b As Boolean
b = False
i = n - 1
Do While i > 0
m(i) = m(i) + 1
If m(i) < n - i + 1 Then Exit Do
m(i) = 0
i = i - 1
Loop
Sum = 0
For i = 1 To n - 1 '计算中介数各位之和
Sum = Sum + m(i)
Next
If Sum = 0 Then b = True '中介数各位之和为0
MedN = b
End Function
3.递减进位制数法
在递增进位制数法中,中介数的最低位是逢2进1,进位频繁,这是一个缺点。把递增进位制数翻转,就得到递减进位制数。
839647521的中介数是67342221(k1k2…kn-1),倒转成为12224376(kn-1…k2k1),这是递减进位制数的中介数: ki(i=n-1,n-2,…,2)位逢i向ki-1位进1。给定排列p,p的下一个排列的中介数定义为p的中介数加1。例如p=839647521,p 的中介数为12224376,p的下一个排列的中介数为12224376+1=12224377,由此得到p的下一个排列为893647521。
给定中介数,可用与递增进位制数法类似的方法还原出排列。但在递减进位制数中,可以不先计算中介数就直接从一个排列求出下一个排列。具体算法如下:
1)如果p(i)=n且i<>n,则p(i)与p(i-1)交换
2)如果p(n)=n,则找出一个连续递减序列9、8、......、i,将其从排列左端删除,再以相反顺序加在排列右端,然后将i-1与左边的数字交换
例如p=893647521的下一个排列是983647521。求983647521的下一个排列时,因为9在最左边且第2位为8,第3位不是7,所以将 8和9从小到大排于最右端364752189,再将7与其左方数字对调得到983647521的下一个排列是367452189。又例如求 987635421的下一个排列,只需要将9876从小到大排到最右端并将5与其左方数字3对调,得到534216789。
程序代码如下:
Private Sub Degr(p() As Integer, ByVal n As Integer)
Dim i As Integer, j As Integer
Do While n > 0
OutL p
If p(1) = n Then '如果第一位是n
i = 0
Do '从左端开始找出最长的连续递降序列
i = i + 1
If i = n Then Exit Sub
Loop Until p(i) <> p(i + 1) + 1
j = i
Do '找出递降序列末尾数字的下一个数字
i = i + 1
Loop Until p(i) = p(j) - 1
Swap p(i), p(i - 1) '将它与序列末尾数字交换
For i = 1 To n - j '将递减序列倒转后放置在排列右端
p(i) = p(i + j)
Next
For i = 1 To j
p(n - i + 1) = n - i + 1
Next
Else '如果最高位不是n
i = 0 '从左端开始
Do '找出n所在位置
i = i + 1
Loop Until p(i) = n
Swap p(i), p(i - 1) '将n与其左边数字交换
End If
Loop
End Sub