一、问题描述
全排列_牛客题霸_牛客网
给定一个字符串,生成该字符串的所有可能排列。例如,对于字符串 "abc"
,其全排列为:
abc
acb
bac
bca
cba
cab
这个问题可以通过递归和回溯算法来解决。递归用于深度优先搜索,回溯用于撤销当前选择,尝试其他可能性。
二、代码实现
以下是实现字符串全排列的C语言代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>
bool used[7] = {0}; // 标记数组,用于记录字符是否被使用
int result[7] = {0}; // 用于存储当前排列的结果
int len = 0; // 字符串长度
// 回溯函数,用于生成全排列
void backtrack(char* str, int deepth) {
if (deepth == len) { // 如果深度等于字符串长度,说明找到一个完整的排列
for (int i = 0; i < deepth; i++) {
printf("%c", result[i]); // 输出当前排列
}
printf("\n");
return;
}
// 尝试每个字符
for (int i = 0; i < len; i++) {
if (!used[i]) { // 如果当前字符未被使用
result[deepth] = str[i]; // 将当前字符加入结果数组
used[i] = true; // 标记当前字符为已使用
backtrack(str, deepth + 1); // 递归调用,进入下一层
used[i] = false; // 回溯,撤销当前选择
}
}
}
int main() {
char str[7];
scanf("%s", str); // 输入字符串
len = strlen(str); // 获取字符串长度
backtrack(str, 0); // 从深度 0 开始递归
return 0;
}
三、代码解析
1. 核心思想
全排列问题的核心在于递归和回溯。递归用于深度优先搜索,回溯用于撤销当前选择,尝试其他可能性。具体逻辑如下:
-
使用一个布尔数组
used
来标记每个字符是否已经被使用。 -
使用一个整型数组
result
来存储当前的排列结果。 -
当递归深度等于字符串长度时,输出当前排列。
-
在每一层递归中,尝试所有未被使用的字符,并标记为已使用,然后进入下一层递归。
-
在回溯时,撤销当前选择,恢复字符的未使用状态。
2. 关键点解析
-
递归终止条件:当递归深度等于字符串长度时,说明已经生成了一个完整的排列。
-
回溯操作:在每次递归返回时,撤销当前选择,恢复字符的未使用状态,以便尝试其他可能性。
回溯示意图:字符串 "abc"
的全排列
1. 初始状态
从根节点开始,递归深度为 0,尚未选择任何字符。
[ ]
2. 第一层递归
从字符串 "abc"
中选择一个字符作为排列的第一个字符。可以选择 a
、b
或 c
。
[ ]
/ | \
[a] [b] [c]
3. 第二层递归
以选择 a
为例,进入第二层递归。此时,a
已被使用,只能从剩余的字符 b
和 c
中选择。
[ ]
/ | \
[a] [b] [c]
/ \
[ab] [ac]
4. 第三层递归
以选择 ab
为例,进入第三层递归。此时,a
和 b
已被使用,只能选择剩余的字符 c
。
[ ]
/ | \
[a] [b] [c]
/ \
[ab] [ac]
/ \
[abc] [acb]
此时,abc
是一个完整的排列,递归终止。
5. 回溯操作
从 abc
开始回溯,撤销选择 c
,回到第二层递归,尝试选择其他字符。
[ ]
/ | \
[a] [b] [c]
/ \
[ab] [ac]
/ \
[abc] [acb]
撤销选择 c
,回到 [ab]
,尝试选择其他字符(但已无其他选择),继续回溯。
[ ]
/ | \
[a] [b] [c]
/ \
[ab] [ac]
/
[abc]
撤销选择 b
,回到 [a]
,尝试选择其他字符(如 c
)。
[ ]
/ | \
[a] [b] [c]
/ \
[ab] [ac]
选择 c
,进入第三层递归,选择剩余的字符 b
。
[ ]
/ | \
[a] [b] [c]
/ \
[ab] [ac]
\
[acb]
此时,acb
是另一个完整的排列,递归终止。剩下以此类推。
回溯示意图(带虚线表示回溯)
以下是完整的回溯树,虚线表示回溯撤销操作:
[ ]
/ | \
[a] [b] [c]
/ \ / \
[ab] [ac] [ba] [bc]
/ \ / \
[abc] [acb] [bac] [bca]
回溯过程:
-
从
[abc]
回溯到[ab]
,撤销选择c
。 -
从
[ab]
回溯到[a]
,撤销选择b
。 -
从
[a]
回溯到根节点,撤销选择a
。 -
从根节点选择
b
,进入[b]
。 -
从
[b]
选择a
,进入[ba]
。 -
从
[ba]
选择c
,生成[bac]
。 -
从
[bac]
回溯到[ba]
,撤销选择c
。 -
从
[ba]
回溯到[b]
,撤销选择a
。 -
从
[b]
选择c
,进入[bc]
。 -
从
[bc]
选择a
,生成[bca]
。 -
从
[bca]
回溯到[bc]
,撤销选择a
。 -
从
[bc]
回溯到[b]
,撤销选择c
。 -
从
[b]
回溯到根节点,撤销选择b
。 -
从根节点选择
c
,进入[c]
。 -
从
[c]
选择a
,进入[ca]
。 -
从
[ca]
选择b
,生成[cab]
。 -
从
[cab]
回溯到[ca]
,撤销选择b
。 -
从
[ca]
回溯到[c]
,撤销选择a
。 -
从
[c]
选择b
,进入[cb]
。 -
从
[cb]
选择a
,生成[cba]
。 -
从
[cba]
回溯到[cb]
,撤销选择a
。 -
从
[cb]
回溯到[c]
,撤销选择b
。 -
从
[c]
回溯到根节点,撤销选择c
。