以前写过全排列的递归算法,思路和实现都比较简单。上周练习python,遇到了全排列的问题,就想着用非递归实现实现以下。没想到从中午想到了半夜,草稿用了好几张,也没想到好的idea。第二天早上在地铁上,在手机拨号页面演示了一会,竟然有了思路。
假定:从小打大排序,称为正序,反之称为逆序,
我的思路是:以数字序列 312为例,排序后123,排列有 [123,132,213,231,312,321],第一项和最后一项分别是正序和逆序,每一项的值递增的。
如果设计一套流程,保证从正序到逆序,按值递增的原则,生成每一项,就完成了这个序列的全排列
那么怎么实现这个递增过程呢,经过一系列的手算,我发现了一些规律:
- 判断是否逆序,如果不是逆序进行下一步,否则,完成排列。
- 从右往左寻找第一个数A,使得A小于它右边相邻的数B
- 从B到往右到末尾寻找C, 使得C是最小的大于A的数。因为A右边的序列(不含A)是有序的,所以只需找到比A小的数的左边的数。如果都大于A,则取最右边一位
- 交换A和C
- B到末尾按正序排序
举几个例子:
例子1:
09442,
1. 一眼看出,不是逆序
2. 从右往左查找第一个小于右邻的数,找到0
3. 然后从9往右找最小的大于0的数为2
4. 0和2交换得:29440
5. 9到末尾正序排序得:20449
从09442到20449,有值位于这两个数之间的排列吗?没有的话,这个就是我们寻找的值。例子2:
38521
1. 非逆序
2. 找到3<8
3. 找到5>3>2,之前说了,3后面是有序的,2<3,后面就不用找了,最小的大于3的就是5
4. 3和5换位得:58321
8到末尾正序重排得:51238例子3:
12
1. 非逆序
2. 1
3. 2
4. 1、2交换得21
5. 2到末尾交换得21
python新手,现学现用,下面是python3实现:
def reverse_at(sstr, i, j):
return sstr[:i] + sstr[i:j + 1][::-1]
def next_permu(sstr):
slen = len(sstr)
tmp = -1
tmp2 = -1
for i in range(0, slen - 1)[::-1]:
if sstr[i] < sstr[i + 1]:
tmp = i
break
if tmp != -1:
for j in range(tmp + 1, len(sstr) - 1):
if sstr[j] > sstr[tmp] and sstr[j + 1] <= sstr[tmp]:
tmp2 = j
if tmp2 == -1:
tmp2 = slen - 1
sstr = swap(sstr, tmp, tmp2)
sstr = reverse_at(sstr, tmp + 1, slen - 1)
return sstr
return None
def swap(str, i, j):
str=list(str)
ch = str[i]
str[i] = str[j]
str[j] = ch
return str
def permutations(sstr):
print(sstr)
lst=sstr
while not is_reversed(sstr):
sstr = next_permu(sstr)
lst+= ' ' + ''.join(sstr)
return lst.split()
def is_reversed(sstr):
for i in range(0, len(sstr) - 1):
if sstr[i] < sstr[i + 1]:
return False
return True
print(permutations(''.join(sorted('bcad'))))