Python 非递归方法的全排列

本文介绍了Python中实现全排列的非递归算法——字典序法,并提供了详细的算法解释和代码示例,适用于对全排列有需求的场景。

python algorithm

全排列(Permutation)


排列Permutation)是将相异物件或符号根据确定的顺序重排。每个顺序都称作一个排列。
例如,从一到六的数字有720种排列,对应于由这些数字组成的所有不重复亦不阙漏的序列,例如4, 5, 6, 1, 2, 3 与1, 3, 5, 2, 4, 6。 – From Wikipedia

nnn个相异元素中取出 kkk个元素,kkk个元素的排列数量为:

Pkn=n!(n−k)! {P_{k}^{n}={\frac {n!}{(n-k)!}}} Pkn=(nk)!n!

其中PPP意为Permutation(排列),!!!表示阶乘运算。全排列而取kkknnn,则结果为n!n!n!

全排列生成算法

  1. 字典序法

    字典序,就是将元素按照字典的顺序(a-z, 1-9)实际上是ASCII编码值进行排列。以字典的顺序作为比较的依据,可以比较出两个串的大小。
    比如 “1” < “13”<“14”<“153”, 就是按每个数字位逐个比较的结果。对于一个串“123456789”“123456789”123456789, 可以知道最小的串是“123456789”“123456789”123456789,而最大的串“987654321”“987654321”987654321。这样针对这个串以字典序法生成全排列生成全排列,就是依次生成
    “123456789”−>“123456798”−>......−>"987654312"−>"987654321"“123456789”->“123456798”->......->"987654312"->"987654321"123456789>123456798>......>"987654312">"987654321"
    这样的串。字典序法要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。

  2. 邻位对换法

    该算法由Johnson-Trotter首先提出,是一个能快速生成全排列的算法。它的下一个全排列总是上一个全排列对换某相邻两位得到的。如果已知n-1个元素的排列,将n插入到排列的不同位置,就得到了n个元素的排列。用这种方法可以产生出任意n个元素的排列。这个方法有一个缺点:为了产生n个元素的排列,我们必须知道并存储所有n-1个元素的排列,然后才能产生出所有n阶排列。

  3. 递增进位制法

    这个算法是基于序列的递增进位制数[3]。递增进位制数是指数字的进制随着位数的递增而递增。一般情况下,数字最右边的进制是2,次右边的进制是3,以此类推。n位递增进位制数一共包含n!个数字,所以它可以与全排列生成算法结合在一起。

  4. 递减进位制法

    该方法与递增进位制法的原理相似,不同的是它定义的“递减进位制数”是数字的进制随着位数的递增而递减。这种进制一般最左边的进制是2,次左边的进制是3。其余原理与递增进位制法基本相同。


Python实现

字典序法
非递归算法

PPP是集合1,2,……n−1,n{1,2,……n-1,n}12n1n的一个全排列:
P=P1P2……Pj−1PjPj+1…Pn(1≤P1,P2,……,Pn≤n)P=P_1P_2……P_{j-1}P_jP_{j+1}…P_n(1≤P_1,P_2,……,P_n≤n)P=P1P2Pj1PjPj+1Pn(1P1P2Pnn)

  1. 从排列的右端开始,找出第一个比右边数字小的数字的序号j,即

    j=max{j∣Pj<Pj+1,1<j<n}j=max\{ j|Pj<Pj+1, 1 < j < n\}j=max{jPj<Pj+1,1<j<n}

    PjP_jPj的右边的数字中,找出所有比PjP_jPj大的数字中最小的数字PkP_kPk,即

    Pk=min{Pi∣Pi>Pj,i>j}P_k=min\{Pi|P_i>P_j,i>j\}Pk=min{PiPi>Pji>j}

  2. 交换PjP_jPjPkP_kPk

  3. 再将排列右端的递减部分Pj+1Pj+2…PnP_{j+1}P_{j+2}…P_nPj+1Pj+2Pn 倒序可以得到一个新的排列

    P′=P1P2…Pj−1PkPn…Pj..Pj+2Pj+1P'=P_1P_2…P_{j-1}P_kP_n…P_j..P_{j+2}P_{j+1}P=P1P2Pj1PkPnPj..Pj+2Pj+1

代码
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
@author: gsharp
"""
def Swap(n,a,b):
    n[a],n[b] = n[b],n[a]
    return None
# 从n[begin]开始反转数组 xxx543->xxx345
def Reverse(n,begin):
    if len(n) > begin:
        i = begin
        j = len(n)-1
        while i < j:
            Swap(n,i,j)
            i += 1
            j -= 1
    return n
#  查找n[i-1:size]中比n[i]大的最小数
def FindMin(n,i):
    j = len(n)-1
    k = i + 1
    while j > i:
        if n[j] > n[i] and n[j] < n[k]:
            k = j
        j -= 1
    return k

def Permut(n):
    count = 0
    j = len(n) -1  
    if j < 1:
        return n
    else :
        print n
        count += 1
        while j >= 1:
            i = j - 1
            if n[i] < n [j] : # 逆序找到第一个小于右侧数的位置i
                k = FindMin(n,i)
                Swap (n,i,k)
                Reverse (n,j)
                j = len(n) - 1
                count += 1
                print n
            else :
                j -= 1
    print count

n =[1,2,3,4,5,6]
Permut(n)

注意:

  1. 这里只能对于具有可比较值的列表排序,对于如【‘~’,’!’,’@’,’#’】无法直接排序。
  2. 初始序列必须为最小序列,否则无法列出全部排列。可先使用快速排序来排序后作为输入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值