数的全排列 - 深度优先搜索
1. 123 的全排列 (3! = 3 * 2 * 1 = 6)
123、132、213、231、312、321。
2. 1234 的全排列 (4! = 4 * 3 * 2 * 1 = 24)
1234、1243、1324、1342、1423、1432,
2134、2143、2314、2341、2413、2431,
3124、3142、3214、3241、3412、3421,
4123、4132、4213、4231、4312、4321。
3. 123 全排列,三重循环嵌套
使用 for a
循环枚举第 1 位,使用 for b
循环枚举第 2 位,使用 for c
循环枚举第 3 位。if
语句进行判断,只有当 a
、b
和 c
互不相等的时候才能输出。
/*
============================================================================
Name : yongqiang.cpp
Author : Yongqiang Cheng
Version : Version 1.0.0
Copyright : Copyright (c) 2020 Yongqiang Cheng
Description : Hello World in C++, Ansi-style
============================================================================
*/
#include <iostream>
#include <time.h>
using namespace std;
int main()
{
clock_t start = 0, end = 0;
double cpu_time_used = 0;
start = clock();
printf("Start of the program, start = %ld\n", start);
printf("Start of the program, start = %ld\n\n", start);
for (int a = 1; a <= 3; a++) {
for (int b = 1; b <= 3; b++) {
for (int c = 1; c <= 3; c++) {
if ((a != b) && (a != c) && (b != c))
{
cout << a << b << c << ", ";
}
}
}
}
end = clock();
printf("\n\nEnd of the program, end_t = %ld\n", end);
printf("End of the program, end_t = %ld\n", end);
cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Total time taken by CPU: %f\n", cpu_time_used);
printf("Exiting of the program...\n");
return 0;
}
4. 1234 全排列,四重循环嵌套
使用 for a
循环枚举第 1 位,使用 for b
循环枚举第 2 位,使用 for c
循环枚举第 3 位,使用 for d
循环枚举第 4 位。if
语句进行判断,只有当 a
、b
、c
和 d
互不相等的时候才能输出。
/*
============================================================================
Name : yongqiang.cpp
Author : Yongqiang Cheng
Version : Version 1.0.0
Copyright : Copyright (c) 2020 Yongqiang Cheng
Description : Hello World in C++, Ansi-style
============================================================================
*/
#include <iostream>
#include <time.h>
using namespace std;
int main()
{
clock_t start = 0, end = 0;
double cpu_time_used = 0;
int num = 0;
start = clock();
printf("Start of the program, start = %ld\n", start);
printf("Start of the program, start = %ld\n\n", start);
for (int a = 1; a <= 4; a++) {
for (int b = 1; b <= 4; b++) {
for (int c = 1; c <= 4; c++) {
for (int d = 1; d <= 4; d++) {
if ((a != b) && (a != c) && (a != d) && (b != c) && (b != d) && (c != d))
{
cout << a << b << c << d << ", ";
num++;
if (0 == num % 6) {
cout << endl;
}
}
}
}
}
}
end = clock();
printf("\nEnd of the program, end_t = %ld\n", end);
printf("End of the program, end_t = %ld\n", end);
cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Total time taken by CPU: %f\n", cpu_time_used);
printf("Exiting of the program...\n");
return 0;
}
5. 深度优先搜索 (不撞南墙不回头)
每一个小盒子都可能放 1 号、2 号或者 3 号扑克牌,这需要一一尝试,for
循环解决。
for (int i = 1; i <= N; i++)
{
// 将 i 号扑克牌放入到第 step 个盒子中
a[step] = i;
}
数组 a
是用来表示小盒子的,变量 step
表示当前正处在第 step
个小盒子面前。a[step] = i;
就是将第 i
号扑克牌放入到第 step
个盒子中。如果一张扑克牌已经放到别的小盒子中了,那么此时就不能再放入同样的扑克牌到当前小盒子中,因为此时手中已经没有这张扑克牌了。因此还需要一个数组 book
来标记哪些牌已经使用了。
for (int i = 1; i <= N; i++)
{
// book[i] 等于 0 表示 i 号扑克牌仍然在手上
if (0 == book[i])
{
// 将 i 号扑克牌放入到第 step 个盒子中
a[step] = i;
// 将 book[i] 设为 1,表示 i 号扑克牌已经不在手上
book[i] = 1;
}
}
现在已经处理完第 step
个小盒子了,接下来需要往下走一步,继续处理第 step + 1
个小盒子。处理方法其实和我们刚刚处理第 step
个小盒子的方法是相同的。把刚才的处理第 step
个小盒子的代码封装为一个函数,我们为这个函数起个名字,就叫做 dfs
。
// step 表示现在站在第几个盒子面前
void dfs(int step)
{
for (int i = 1; i <= N; i++)
{
// 判断扑克牌 i 是否还在手上,book[i] 等于 0 表示 i 号扑克牌仍然在手上
if (0 == book[i])
{
// 将 i 号扑克牌放入到第 step 个盒子中
a[step] = i;
// 将 book[i] 设为 1,表示 i 号扑克牌已经不在手上
book[i] = 1;
}
}
}
在处理完第 step
个小盒子之后,紧接着处理第 step + 1
个小盒子,处理第 step + 1
个小盒子的方法就是 dfs(step + 1)
。
// step 表示现在站在第几个盒子面前
void dfs(int step)
{
for (int i = 1; i <= N; i++)
{
// 判断扑克牌 i 是否还在手上,book[i] 等于 0 表示 i 号扑克牌仍然在手上
if (0 == book[i])
{
// 将 i 号扑克牌放入到第 step 个盒子中
a[step] = i;
// 将 book[i] 设为 1,表示 i 号扑克牌已经不在手上
book[i] = 1;
// 通过函数的递归调用来实现 (自己调用自己)
dfs(step + 1);
// 一定要将刚才尝试的扑克牌收回,才能进行下一次尝试
book[i] = 0;
}
}
}
上面代码中的 book[i] = 0
非常重要,这句话的作用是将小盒子中的扑克牌收回。在一次摆放尝试结束返回的时候,如果不把刚才放入小盒子中的扑克牌收回,那将无法再进行下一次摆放。其实当我们处理到第 N + 1
个小盒子的时候 (即 step
等于 N + 1
),那么说明前 N
个盒子都已经放好扑克牌了,这里就将 1 ~ N
个小盒子中的扑克牌编号打印出来就可以了。打印完毕一定要立即 return
。
// step 表示现在站在第几个盒子面前
void dfs(int step)
{
// 如果站在第 N + 1 个盒子面前,则表示前 N 个盒子已经放好扑克牌
if ((N + 1) == step)
{
// 输出一种排列 (1~N 号盒子中的扑克牌编号)
for (int i = 1; i <= N; i++)
{
cout << a[i];
}
// 返回之前的一步 (最近一次调用 dfs 函数的地方)
return;
}
for (int i = 1; i <= N; i++)
{
// 判断扑克牌 i 是否还在手上,book[i] 等于 0 表示 i 号扑克牌仍然在手上
if (0 == book[i])
{
// 将 i 号扑克牌放入到第 step 个盒子中
a[step] = i;
// 将 book[i] 设为 1,表示 i 号扑克牌已经不在手上
book[i] = 1;
// 通过函数的递归调用来实现 (自己调用自己)
dfs(step + 1);
// 一定要将刚才尝试的扑克牌收回,才能进行下一次尝试
book[i] = 0;
}
}
}
/*
============================================================================
Name : yongqiang.cpp
Author : Yongqiang Cheng
Version : Version 1.0.0
Copyright : Copyright (c) 2020 Yongqiang Cheng
Description : Hello World in C++, Ansi-style
============================================================================
*/
#include <iostream>
#include <time.h>
using namespace std;
// 全局变量默认值为 0,不必再次赋初始值 0
int a[10] = { 0 };
int book[10] = { 0 };
int N = 0;
// step 表示现在站在第几个盒子面前
void dfs(int step)
{
// 如果站在第 n + 1 个盒子面前,则表示前 n 个盒子已经放好扑克牌
if ((N + 1) == step)
{
// 输出一种排列 (1~N 号盒子中的扑克牌编号)
for (int i = 1; i <= N; i++)
{
cout << a[i];
}
cout << endl;
// 返回之前的一步 (最近一次调用 dfs 函数的地方)
return;
}
// 按照 1, 2, 3, ...N 的顺序一一尝试
for (int i = 1; i <= N; i++)
{
// 判断扑克牌 i 是否还在手上,book[i] 等于 0 表示 i 号扑克牌仍然在手上
if (0 == book[i])
{
// 将 i 号扑克牌放入到第 step 个盒子中
a[step] = i;
// 将 book[i] 设为 1,表示 i 号扑克牌已经不在手上
book[i] = 1;
// 第 step 个盒子已经放好扑克牌,接下来走到下一个盒子面前
// 通过函数的递归调用来实现 (自己调用自己)
dfs(step + 1);
// 一定要将刚才尝试的扑克牌收回,才能进行下一次尝试
book[i] = 0;
}
}
return;
}
int main()
{
clock_t start = 0, end = 0;
double cpu_time_used = 0;
start = clock();
printf("Start of the program, start = %ld\n", start);
printf("Start of the program, start = %ld\n\n", start);
int start_num = 1;
// 输入 [1, 9] 之间的整数 N
cin >> N;
dfs(start_num);
end = clock();
printf("\nEnd of the program, end_t = %ld\n", end);
printf("End of the program, end_t = %ld\n", end);
cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Total time taken by CPU: %f\n", cpu_time_used);
printf("Exiting of the program...\n");
return 0;
}
上面的例子是深度优先搜索 (Depth First Search,DFS) 的基本模型,理解深度优先搜索的关键在于解决“当下该如何做”,至于“下一步如何做”则与“当下该如何做”是一样的,上面 dfs(step)
这个函数就是为了解决当你在第 step
个箱子面前的时候,你会怎么放扑克牌或者结束了投放操作,下一步也是这样的操作。如果要遍历所有的可能性,可以用 for
循环来遍历所有的可能性。当前的步骤 step
完成后就进入下一个 step
(step = step+1
),下一步的解决办法和当前的解决办法是一致的。
void dfs(int step)
{
STEP 1. 判断边界,返回
STEP 2. 尝试每一种可能
for (int i = 1; i <= N; i++)
{
继续下一步 dfs(step + 1);
}
STEP 3. 返回
}