[算法][递归/回溯]递归实现排列型枚举

递归实现排列型枚举

把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数 n。

输出格式

按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

数据范围

1 ≤ n ≤ 9

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

解题思路

思路一

递归回溯法

n = int(input())
status = [0]  *(n+1) # 1-n表述存储了哪些数
used = [0] * (n+1) #用于全排列的元素是否用过
def dfs(u):
    if u > n: # 全选之后 进行输出
        for i in status[1:]:
            print(i, end=" ")
        print()
        return

    for i in range(1, n+1):
        if used[i] == 0: # 如果该位没有被选择
            status[u] = i # 记录下选中的是什么数字
            used[i] = 1 # 该位已选,下次不会重复选中

            dfs(u+1)

            status[u] = 0 # 只有全选之后才会执行这行代码,所以需要归零
            used[i] = 0 

dfs(1)

递归树

ROOT (dfs(1), 已用数字: [], 剩余: [1,2,3])
│
├─ 分支1: 选择1 → dfs(2) (已用数字: [1], 剩余: [2,3])
│  │
│  ├─ 分支2: 选择2 → dfs(3) (已用数字: [1,2], 剩余: [3])
│  │  │
│  │  └─ 分支3: 选择3 → dfs(4) → 输出 [1,2,3] ↲
│  │
│  └─ 分支2: 选择3 → dfs(3) (已用数字: [1,3], 剩余: [2])
│     │
│     └─ 分支3: 选择2 → dfs(4) → 输出 [1,3,2] ↲
│
├─ 分支2: 选择2 → dfs(2) (已用数字: [2], 剩余: [1,3])
│  │
│  ├─ 分支1: 选择1 → dfs(3) (已用数字: [2,1], 剩余: [3])
│  │  │
│  │  └─ 分支3: 选择3 → dfs(4) → 输出 [2,1,3] ↲
│  │
│  └─ 分支3: 选择3 → dfs(3) (已用数字: [2,3], 剩余: [1])
│     │
│     └─ 分支1: 选择1 → dfs(4) → 输出 [2,3,1] ↲
│
└─ 分支3: 选择3 → dfs(2) (已用数字: [3], 剩余: [1,2])
   │
   ├─ 分支1: 选择1 → dfs(3) (已用数字: [3,1], 剩余: [2])
   │  │
   │  └─ 分支2: 选择2 → dfs(4) → 输出 [3,1,2] ↲
   │
   └─ 分支2: 选择2 → dfs(3) (已用数字: [3,2], 剩余: [1])
      │
      └─ 分支1: 选择1 → dfs(4) → 输出 [3,2,1] ↲

思路二

字典序生成法

假设当前排列为 nums,生成下一个排列的规则如下:

  1. 从右向左找第一个升序对
    找到最大的索引 i,使得 nums[i] < nums[i+1]。如果找不到,说明当前排列已是最大字典序,结束循环。
  2. 从右向左找第一个比 nums[i] 大的数
    找到最大的索引 j,使得 nums[j] > nums[i]
  3. 交换 nums[i]nums[j]
    此时,nums[i+1:] 是降序排列的。
  4. 反转 nums[i+1:]
    nums[i+1:] 反转,使其变为升序,得到下一个排列。

示例:n=3 的生成过程

初始排列为 [1,2,3],后续步骤如下:

当前排列 i(升序对) j(比 nums[i] 大) 交换后 反转 i+1 下一个排列
1 2 3 i=1 (2 < 3) j=2 (3 > 2) 1 3 2 不反转 1 3 2
1 3 2 i=0 (1 < 3) j=1 (3 > 1) 3 1 2 反转 1→2 →2 1 2 1 3
2 1 3 i=1 (1 < 3) j=2 (3 > 1) 2 3 1 反转 3→1 →1 3 2 3 1
2 3 1 i=0 (2 < 3) j=2 (1 > 2 不成立) → j=1 3 2 1 反转 2→1 →1 2 3 1 2
3 1 2 i=1 (1 < 2) j=2 (2 > 1) 3 2 1 反转 2→1 →1 2 3 2 1
3 2 1 找不到 i → 结束

代码实现


n = int(input())
nums = list(range(1, n + 1))  # 初始排列是升序

while True:
    print(' '.join(map(str, nums)))
    # 步骤1:从右向左找第一个升序对 (i)
    i = n - 2
    while i >= 0 and nums[i] >= nums[i + 1]:
        i -= 1
    if i == -1:
        break  # 已是最大排列,结束

    # 步骤2:从右向左找第一个比 nums[i] 大的数 (j)
    j = n - 1
    while nums[j] <= nums[i]:
        j -= 1

    # 步骤3:交换 nums[i] 和 nums[j]
    nums[i], nums[j] = nums[j], nums[i]

    # 步骤4:反转 nums[i+1:](改为升序)
    nums[i+1:] = nums[i+1:][::-1]
posted @ 2025-03-03 22:17  zoom&3  阅读(13)  评论(2编辑  收藏  举报