当前位置:首页算法 > 正文

【回溯与搜索】用伪代码把下面题目解答一下:设有n个整数的集合{1,2,......,n},从中任意取出r个数进行排列(r<n),试列出所有的排列。

作者:野牛程序员:2023-05-04 17:43:01算法阅读 2455

用伪代码把下面题目解答一下:设有n个整数的集合{1,2,......,n},从中任意取出r个数进行排列(r<n),试列出所有的排列。

以下是一种可能的伪代码实现:

function permutations(A, n, r, index, used):
    if index = r:
        print current permutation
        return
    for i from 1 to n:
        if not used[i]:
            A[index] = i
            used[i] = true
            permutations(A, n, r, index+1, used)
            used[i] = false

initialize array A with r elements
initialize boolean array used with n elements
permutations(A, n, r, 0, used)

解释一下这个伪代码的思路:

  • 我们首先定义一个函数 permutations(A, n, r, index, used),其中 A 是一个长度为 r 的数组,用于存储当前已选的元素,n 表示集合的大小,r 表示要选出的元素个数,index 表示当前已选了几个元素,used 是一个长度为 n 的布尔数组,用于标记哪些元素已经被选过了。

  • 如果 index 等于 r,说明已经选出了 r 个元素,可以输出当前的排列结果。

  • 如果 index 小于 r,我们就从集合中选取一个元素放到当前位置(即下标为 index 的位置),并递归地生成剩下 r-1 个元素的排列结果。递归完成后,我们需要将当前位置的元素标记为未使用,以便回溯到上一层递归时可以重新选择该元素。

  • 最后,我们初始化一个长度为 r 的数组 A,一个长度为 n 的布尔数组 used,并将 used 中所有元素初始化为 false。然后调用 permutations(A, n, r, 0, used) 生成所有的排列结果。

这个算法的时间复杂度为 O(n!/(n-r)!),即生成所有排列的时间复杂度。


上面伪代码的递归回溯之数据演示:


假设我们要从集合 {1, 2, 3} 中任意取出 2 个数进行排序,以下是演示过程:

  1. 调用 permutations(A, n, r, 0, used),其中 A 是一个长度为 r 的数组,n 为 3,r 为 2,used 是一个长度为 3 的布尔数组,所有元素都为 false

  2. 进入 permutations 函数,index 为 0,还没有选择任何元素,因此进入 for 循环。第一次循环,我们选择元素 1,将其存储到 A[0] 中,并将 used[1] 标记为 true,然后递归调用 permutations(A, n, r, 1, used)

A = [1, _, _]
used = [true, false, false]


3、进入新的 permutations 调用,index 为 1,已经选择了元素 1。因为还需要选择 1 个元素,因此进入 for 循环。第一次循环,发现元素 1 已经被使用了,因此跳过。第二次循环,我们选择元素 2,将其存储到 A[1] 中,并将 used[2] 标记为 true,然后进入下一层递归。

A = [1, 2, _]
used = [true, true, false]

4、进入新的 permutations 调用,index 为 2,已经选择了元素 1 和 2。因为已经选择了 2 个元素,可以输出当前的排列结果。

A = [1, 2, _]
used = [true, true, false]

5、因为已经输出了一个排列结果,返回到上一层递归,即 permutations(A, n, r, 1, used)。在这一层递归中,我们选择元素 3,将其存储到 A[1] 中,并将 used[3] 标记为 true,然后进入下一层递归。

A = [1, 3, _]
used = [true, false, true]

6、进入新的 permutations 调用,index 为 2,已经选择了元素 1 和 3。因为已经选择了 2 个元素,可以输出当前的排列结果。

A = [1, 3, _]
used = [true, false, true]

7、因为已经输出了一个排列结果,返回到上一层递归,即 `permutations(A, n, r, 1, used)


8、在 permutations(A, n, r, 1, used) 中,第二次循环结束后,因为没有更多的元素可供选择,所以直接返回到上一层递归,即 permutations(A, n, r, 0, used)


9、在permutations(A, n, r, 0, used) 中,第二次循环结束后,因为没有更多的元素可供选择,所以直接返回到上一层递归,即程序结束。

最终的输出结果为:

[1, 2]
[1, 3]
[2, 1]
[2, 3]
[3, 1]
[3, 2]

可以看到,我们成功地输出了集合 {1, 2, 3} 中任意取出 2 个数的所有排列。


自然语言描叙一下:


以下是对上述伪代码的递归回溯过程的自然语言描述:

  1. 首先定义一个递归函数 permutations(A, n, r, index, used),其中 A 是一个长度为 r 的数组,用于存储当前的排列结果,n 表示集合的大小,r 表示需要选择的元素个数,index 表示当前已经选择了多少个元素,used 是一个长度为 n 的布尔数组,用于记录哪些元素已经被选择了。

  2. permutations 函数中,首先判断当前排列结果的长度是否已经达到 r。如果是,就将当前的排列结果输出,并结束函数。

  3. 如果当前排列结果的长度还没有达到 r,那么从集合中选择一个还没有被使用过的元素,加入到当前的排列结果中,并将该元素的使用状态标记为已使用。

  4. 然后递归调用 permutations 函数,处理下一个位置的元素。

  5. 当递归调用结束后,需要将当前选择的元素的使用状态标记为未使用,以便后续的递归调用可以使用该元素。

  6. 重复执行步骤 3~5,直到所有的排列结果都被输出。

  7. 在递归回溯过程中,每一层递归调用都会对一个位置进行选择并递归下去,直到所有的位置都被选择完成。如果发现某一个位置没有可以选择的元素了,那么就需要返回上一层递归并继续选择其他的元素,直到所有的排列结果都被输出。


下面是C++代码一:

#include <iostream>
#include <vector>
using namespace std;

void permutations(vector<int>& A, int n, int r, int index, vector<bool>& used) {
    if (index == r) { // 如果已经选择了 r 个元素,输出当前的排列结果
        for (int i = 0; i < r; i++) {
            cout << A[i] << " ";
        }
        cout << endl;
        return;
    }

    for (int i = 1; i <= n; i++) {
        if (!used[i]) { // 如果元素没有被使用过,那么选择该元素
            A[index] = i;
            used[i] = true;

            permutations(A, n, r, index + 1, used); // 递归处理下一个位置的元素

            used[i] = false; // 回溯到当前层,将当前选择的元素的使用状态标记为未使用
        }
    }
}

int main() {
    int n = 3, r = 2;
    vector<int> A(r); // 存储当前的排列结果
    vector<bool> used(n + 1, false); // 记录哪些元素已经被使用了

    permutations(A, n, r, 0, used);

    return 0;
}

代码中使用了 vector 来存储排列结果和记录元素使用状态,同时使用了递归回溯的思想来枚举所有的排列。


下面是C++代码二:

#include <iostream>
using namespace std;

const int MAXN = 100;
int A[MAXN]; // 存储当前的排列结果
bool used[MAXN]; // 记录哪些元素已经被使用了

void permutations(int n, int r, int index) {
    if (index == r) { // 如果已经选择了 r 个元素,输出当前的排列结果
        for (int i = 0; i < r; i++) {
            cout << A[i] << " ";
        }
        cout << endl;
        return;
    }

    for (int i = 1; i <= n; i++) {
        if (!used[i]) { // 如果元素没有被使用过,那么选择该元素
            A[index] = i;
            used[i] = true;

            permutations(n, r, index + 1); // 递归处理下一个位置的元素

            used[i] = false; // 回溯到当前层,将当前选择的元素的使用状态标记为未使用
        }
    }
}

int main() {
    int n = 3, r = 2;
    permutations(n, r, 0);

    return 0;
}

代码中使用了静态数组来存储排列结果和记录元素使用状态,同时使用了递归回溯的思想来枚举所有的排列。


完整跟踪执行结果:

初始状态:

n = 3
r = 2
A = [0, 0, ..., 0] (长度为 r)
used = [false, false, ..., false] (长度为 n)

第一次递归调用 permutations(n, r, 0)

index = 0

for i = 1:
  A[0] = 1
  used[1] = true
  第二次递归调用 permutations(n, r, 1):
    index = 1

    for i = 1:
      A[1] = 2
      used[1] = false
      返回第一次递归调用
    for i = 2:
      A[1] = 2
      used[2] = true
      输出 A = [1, 2]
      used[2] = false
      返回第一次递归调用
    for i = 3:
      A[1] = 3
      used[3] = true
      输出 A = [1, 3]
      used[3] = false
      返回第一次递归调用

  used[1] = false

for i = 2:
  A[0] = 2
  used[2] = true
  第二次递归调用 permutations(n, r, 1):
    index = 1

    for i = 1:
      A[1] = 1
      used[1] = true
      输出 A = [2, 1]
      used[1] = false
      返回第一次递归调用
    for i = 2:
      A[1] = 2
      used[2] = false
      返回第一次递归调用
    for i = 3:
      A[1] = 3
      used[3] = true
      输出 A = [2, 3]
      used[3] = false
      返回第一次递归调用

  used[2] = false

for i = 3:
  A[0] = 3
  used[3] = true
  第二次递归调用 permutations(n, r, 1):
    index = 1

    for i = 1:
      A[1] = 1
      used[1] = true
      输出 A = [3, 1]
      used[1] = false
      返回第一次递归调用
    for i = 2:
      A[1] = 2
      used[2] = true
      输出 A = [3, 2]
      used[2] = false
      返回第一次递归调用
    for i = 3:
      A[1] = 3
      used[3] = false
      返回第一次递归调用

  used[3] = false

最终的输出结果为:

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


野牛程序员教少儿编程与信息学奥赛-微信|电话:15892516892
野牛程序员教少儿编程与信息学竞赛-微信|电话:15892516892
相关推荐

最新推荐

热门点击