數的全排列 - 深度優先搜索

數的全排列 - 深度優先搜索

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 語句進行判斷,只有當 abc 互不相等的時候才能輸出。

/*
============================================================================
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 語句進行判斷,只有當 abcd 互不相等的時候才能輸出。

/*
============================================================================
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. 返回
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章