全排列

1 全排列

1.1 含义

全排列,即给定一个集合,输出集合中元素所有组合的情况。

比如给定集合{1,2,3},应该输出:

123
132
213
231
312

321


1.2 递归的方式

思路1:

尝试给lst数组的第i个位置添加元素,如果i位置前没出现过该元素即可以添加该元素,如果出现过该元素,那么尝试另一个元素,如果成功了,那么递归的尝试给i+1位置添加元素,最终,如果所有位置的元素都被填满了,那么递归结束。


代码:

def permutation(lst, n, cur):
    if cur == n:  # 所有位置均被填满则输出
        print lst
    else:
        for i in range(1, n+1):  # 对当前位置尝试所有元素,看看能不能填入
            flag = True  # flag为标示,如果某元素可以填入,那么flag将保持True值不变
            for j in range(0, cur):  # 判断当前位置前的元素是否出现过想添加的元素i,如果出现过则不添加,否则可以添加
                if lst[j] == i:
                    flag = False
                    break
            if flag:
                lst[cur] = i
                permutation(lst, n, cur+1)  # 递归的添加下一个位置


SIZE = 5
permutation([0]*SIZE, SIZE, 0)


注意:

上面的代码输出的是[1,2,3 ... n]的全排列,对于给定集合如['a', 'b', 'c', 'c']的全排列需要对代码进行修改或转换思路,比如,将上面字母的排列看成是它们位置的排列,而位置的排列可以直接使用上面的代码生成位置的排列,然后对应成对应的字符,当然,有时需要去重。


思路2:

对于给定序列的排列,也可以使用交换的方式进行排列。首先将i位置后面的所有元素依次与i位置的元素进行交换,再递归进行i+1位置与i+1后面所有的元素进行交换,如果这种交换进行到了序列的末尾,则进行输出返回,另外,在递归返回后需要重新将交换的元素再次互换,即恢复到未交换之前的状态,否则后面的交换将出错。


代码:

def permutation(lst, k, m):
    if k == m-1:  # 如果交换到了最后一个位置,那么输出序列,递归结束
        print ''.join(lst)  
    else:
        for i in range(k, m):  # 依次将k位置后的元素与k位置的元素进行互换
            lst[k],lst[i] = lst[i],lst[k]
            permutation(lst, k+1, m)  # 递归的进行下一个位置
            lst[k],lst[i] = lst[i],lst[k]  # 递归返回后需要恢复序列原先的位置,供下一轮互换


SIZE = 3
permutation(['a', 'c', 'b'], 0, SIZE)


1.3 非递归的方式

本非递归全排列算法为字典序排列算法,借鉴的是C++ STL中的next_permutation算法的思想。

其基本思想是:

  1. 对初始序列进行排列,找到所有排列中的最小的一个排列P0
  2. 找到比P0刚刚大一点的排列P1,P1比除P0以外的排列都小
  3. 循环执行第二步,直到找到一个最大的排列,算法结束

比如有序列‘12345’,它的最小的排列为‘12345’,比‘12345’稍微大一点的排列为‘12354’,接着是‘12435’ ... 一直到‘54321’。


算法思想:

对于给定的序列P = [A1, A2, A3 ... An] 首先对P按字典排序,得到P的最小的排列P0 = [A1, A2, A3 ... An],满足A1 < A2 < ... < An.

接着按如下步骤找到P的下一个排列。

  1. 从后向前,找到第一对为升序的相邻元素,即Ai < A(i+1),如果找不到则说明此时的序列为最大排列,已经找到了全部的全排列,可以退出了。
  2. 从后向前,找到第一个比Ai大的数Aj,交换Ai和Aj。
  3. 将Ai后面的数全部逆序倒置,由前面的步骤1和2知,交换后的Ai后面的数均为升序序列,这样逆序倒置后可以保证所得到的新的排列刚刚比上一个排列大。
  4. 重复步骤1-3,直到找到最大的排列。

比如当前的序列状态为:[4, 5, 3, 2, 1],第一步,我们发现找到的第一对升序的相邻元素为4和5,i = 0;第二步,我们发现比4大的元素为5,交换后得到序列[5, 4, 3, 2, 1];第三步,我们将i = 0后面的所有元素逆序倒置,得到[5, 1, 2, 3, 4],而该序列即为刚刚好比上一个序列[4, 5, 3, 2, 1]大的序列,输出;第四步,重复。


代码:

def reverse(lst, i, j):
    '将lst中下标i到j之间的所有元素逆序倒置'
    while i < j:
        lst[i],lst[j] = lst[j],lst[i]
        i += 1
        j -= 1


def permutation(lst):
    lens = len(lst)
    if lens < 2:
        return

    while True:
        print lst
        i = lens - 2
        while i >= 0:
            if lst[i] < lst[i+1]:  # 找到第一对相邻升序对
                break
            elif i == 0:
                return
            i -= 1

        j = lens - 1
        while j > i:
            if lst[j] > lst[i]:  # 找到找到第一个比lst[i]大的元素,下一步进行交换
                break
            j -= 1

        lst[i],lst[j] = lst[j],lst[i]
        reverse(lst, i+1, lens-1)



lst = [1,2,3,4,5]
permutation(lst)


1.4 permutations

事实上,python的itertools模块中提供了permutations函数,类似于C++的STL提供的库函数next_permutation,能够将给定的列表或者字符串进行全排列,返回的是一个迭代器。

利用该函数,我们可以轻易的进行给定列表的全排列,代码如下:

import itertools

s = 'abcc'
g = itertools.permutations(s)
# 升序并且需要去重,否则对于串“abcc”,会出现两次“abcc”,函数会将两个c当作不同的字母
g = sorted(set(g))
for i in g:
    print ''.join(i)


你可能感兴趣的:(python,全排列)