Python - 求数组的全排列

30 篇文章 1 订阅

一.引言

给定一个正整数 N,输出数列 1,2,3,...,N 的全排列。简单分析一下,N 个正整数,总共满足的可能性为 N!= N * (N-1) * ... * 1。通过遍历每一个位置,分别放置不同的数字即可达到问题要求。

先用一个最 for 的方案理解一下:

    nums = [1, 2, 3]
    candidates = []
    for i in nums:
        for j in nums:
            for k in nums:
                if i != j and i != k and j != k:
                    candidates.append(str(i) + str(j) + str(k))

 第一位固定1,对剩下的两个数字分别固定,以此类推,这种方法适合写少量循环,如果数组元素太多 if 条件写起来比较麻烦。

123
132
213
231
312
321

二.回朔

第一个位置有N中可能,对于每一个可能,固定第一位数字,对后面 N-1 个数字进行全排列,依次推进,直到对最后 1 个数字全排列,因此可以通过递归和回朔解决。

def solveByBackTrack(nums):
    def backtrack(position, end):

        if position == end:
            res.append(nums[:])
            return

        for index in range(position, end):
            nums[index], nums[position] = nums[position], nums[index]
            backtrack(position + 1, end)
            nums[index], nums[position] = nums[position], nums[index]

    res = []
    backtrack(0, len(nums))
    return list(map(lambda arr: ''.join(list(map(lambda x: str(x), arr))), res))

A.递归与回朔: 

backtrack 方法从 position 0 开始,第一个 for 循环将0与其他所有位置交换,相当于固定第一个元素,此时有 N 种可能,因为每一个数字都被换到了第一位,随后对当前数组的剩下 N-1 位置进行 backtrack,道理同上,注意 backtrack 回朔时返回上一个状态。

B.退出条件

position 换到最后一个位置,即最后只能用 position 的元素和 end 的元素调换位置,说明当前只剩一个元素,当前排列完成,退出。

三.深度搜索

第一个位置有N种可能,对于每一个可能,当做一个根节点,后面 N-1 个数字都可以是当前根节点的 node,继续推荐,这 N-1 个 node 每个节点下都有 N-2 个 node,依次往下,因此可以通过DFS 解决。

def solveByDFS(nums):
    visit = [True for _ in range(len(nums))]
    tmp = nums[:]

    def dfs(position):
        if position == len(nums):
            res.append(tmp[:])
            return

        for index in range(0, len(nums)):
            if visit[index]:
                tmp[position] = nums[index]
                visit[index] = False
                dfs(position + 1)
                visit[index] = True

    res = []
    dfs(0)
    return list(map(lambda arr: ''.join(list(map(lambda x: str(x), arr))), res))

A.深度搜索

这里使用了长度为 N 的数组进行状态标记,第一个 for 循环固定 1-N 个数字,访问过后的节点标记为 False,随后 dfs(position + 1) 向后续为 True 的点进行探索,直到 position = len(nums)。

B.退出条件

position 访问至 len(nums) 时,只有最后一个元素为 Ture,将此位置标记为 False,并进入 dfs(position + 1),此时将结果存至 res 并返回。

四.暴力解法

总共有 N! 种可能,构造一个 Set,使用 random.shuffle 对数组进行打散并添加到 Set 中,当Set.size = N! 时候代表全排列全部找到。

def lazySolution(nums):
    candidate = set()
    nums2str = list(map(lambda x: str(x), nums))
    total = reduce(lambda x, y: x * y, nums)

    while True:
        if len(candidate) >= total:
            break
        random.shuffle(nums2str)
        candidate.add("".join(nums2str))
    re = sorted(candidate)
    return re

A.退出条件

总共 N! 种可能, Set 可以去重,所以当 Set 的 size 达到全部可能数时,标志全排列完成。

五.测试

def cost(n, epoch):
    back_cost = 0
    dfs_cost = 0
    lazy_cost = 0

    for i in range(epoch):
        _nums = list(range(1, n + 1))
        import time
        time1 = time.time()
        print(solveByBackTrack(_nums))
        time2 = time.time()
        print(solveByDFS(_nums))
        time3 = time.time()
        print(lazySolution(_nums))
        time4 = time.time()
        back_cost += time2 - time1
        dfs_cost += time3 - time2
        lazy_cost += time4 - time3

    print("回朔平均耗时:", float(back_cost) / epoch)
    print("DFS平均耗时:", float(dfs_cost) / epoch)
    print("包里搜索平均耗时:", float(lazy_cost) / epoch)

遍历数组选择 1-7,跑50轮测平均速度:

回朔平均耗时: 0.009738812446594238
DFS平均耗时: 0.010725607872009277
暴力搜索平均耗时: 0.0832136631011963

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BIT_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值