數的全排列 - 深度優先搜索
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. 返回
}