算法基礎知識——搜索

算法基礎知識——搜索

目錄:

  1. 基礎知識
    1. 廣度優先搜索(求解最優值)
    2. 深度優先搜索(判斷解存在)
  2. 應用實例
    1. Catch That Cow【HDOJ 2717】
    2. Find The Multiple【POJ 1426】
    3. 瑪雅人的密碼【清華大學】
    4. A Knight's Journey【POJ 2488】
    5. Square【University of Waterloo Local Contest 2002.09.21】
    6. 神奇的口袋【北京大學】
    7. 八皇后【北京大學】

一、基礎知識

1、廣度優先搜索(Breadth First Search,BFS):

  • 定義:將已發現結點和未發現結點之間的邊界,沿其廣度方向向外擴展。算法需要在發現所有距離源結點s爲k的所有結點之後,纔會發現距離源結點s爲k+1的其他結點。
  • 步驟:首先訪問起點,然後依次訪問起點尚未訪問的鄰居結點,再按照訪問起點鄰居結點的先後順序依次訪問它們的鄰居,直到找到解或搜遍整個解空間。
  • 特性:獲得一個狀態後,立即擴展這個狀態,並且保證早得到的狀態先擴展。使用隊列的先進先出特性來實現得到的狀態先擴展這一特性。
    • 將得到的狀態依次放入隊尾,每次取隊頭元素進行擴展。
    • 標記有效狀態和無效狀態,避免重複擴展。
  • 應用場景:常用於求解最優值問題。
  • 應用廣度優先搜索思想的算法:Prim最小生成樹算法、Dijkstra單源最短路徑算法
  • 運行時間:O(V + E)

2、深度優先搜索(Depth First Search,DFS):

  • 定義:總是對最近才發現的結點v的出發邊進行探索,直到該結點的所有出發邊都被發現爲止。
  • 步驟:首先訪問起點,之後訪問起點的一個鄰居,先不訪問除該點之外的其他起點的鄰居結點,而是訪問該點的鄰居結點,如此往復,直到找到解,或者當前訪問結點已經沒有尚未訪問過的鄰居結點爲止,之後回溯到上一個結點並訪問它的另一個鄰居結點。
  • 特性:獲得一個狀態後,立即擴展這個狀態,並且保證早得到的狀態較後得到擴展。常常使用遞歸或棧的策略來實現。
  • 應用場景:常用於判斷一個問題的解是否存在。
  • 運行時間:θ(V + E)

二、應用實例

1、題目描述:Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a number line and the cow is at a point K (0 ≤ K ≤ 100,000) on the same number line. Farmer John has two modes of transportation: walking and teleporting.
* Walking: FJ can move from any point X to the points X - 1 or X + 1 in a single minute
* Teleporting: FJ can move from any point X to the point 2 × X in a single minute.
If the cow, unaware of its pursuit, does not move at all, how long does it take for Farmer John to retrieve it?【HDOJ 2717】

  • 輸入格式:Line 1: Two space-separated integers: N and K
  • 輸出格式:Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.
  • 提示信息:The fastest way for Farmer John to reach the fugitive cow is to move along the following path: 5-10-9-18-17, which takes 4 minutes.
  • 樣例輸入:
    • 5 17
  • 樣例輸出:
    • 4

示例代碼:

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

const int MAX_N = 100001;

struct Node{
	int loc;
	int count;
	Node(){};
	Node(int l, int c = 0):loc(l), count(c){};
};

queue<Node> nodeQueue;
int visit[MAX_N];

int BFS(int n, int k){
	while(!nodeQueue.empty()){
		Node node = nodeQueue.front();
		if(node.loc == k){
			return node.count;
		}
		nodeQueue.pop();
		if(node.loc - 1 >= 0 && visit[node.loc - 1] == 0){
			visit[node.loc - 1] = 1;
			nodeQueue.push(Node(node.loc - 1, node.count + 1));
		}
		if(node.loc * 2 < MAX_N && visit[node.loc * 2] == 0){
			visit[node.loc * 2] = 1;
			nodeQueue.push(Node (node.loc * 2, node.count + 1));
		}
		if(node.loc + 1 < MAX_N && visit[node.loc + 1] == 0){
			visit[node.loc + 1] = 1;
			nodeQueue.push(Node(node.loc + 1, node.count + 1));
		}
	}
}

int main(){
	int N, K;
	while(cin >> N >> K){
		memset(visit, 0, sizeof(visit));
		nodeQueue.push(Node(N));
		int result = BFS(N, K);
		cout << result << endl;
		while(!nodeQueue.empty()){
			nodeQueue.pop();
		}
	}
	return 0;
}

2、題目描述:Given a positive integer n, write a program to find out a nonzero multiple m of n whose decimal representation contains only the digits 0 and 1. You may assume that n is not greater than 200 and there is a corresponding m containing no more than 100 decimal digits.【POJ 1426】

  • 輸入格式:The input file may contain multiple test cases. Each line contains a value of n (1 <= n <= 200). A line containing a zero terminates the input.
  • 輸出格式:For each value of n in the input print a line containing the corresponding value of m. The decimal representation of m must not contain more than 100 digits. If there are multiple solutions for a given value of n, any one of them is acceptable.
  • 樣例輸入:
    • 2
    • 6
    • 19
    • 0
  • 樣例輸出:
    • 10
    • 100100100100100100
    • 111111111111111111

示例代碼:

#include <iostream>
#include <queue>

using namespace std;

long long BFS(int n){
	queue<long long> myQueue;
	myQueue.push(1);
	while(!myQueue.empty()){
		long long number = myQueue.front();
		myQueue.pop();
		if(number % n == 0){
			return number;
		}
		myQueue.push(number * 10);
		myQueue.push(number * 10 + 1);
	}
}

int main(){
	int n;
	while(cin >> n && n != 0){
		cout << BFS(n) << endl;
	}
	return 0;
}

3、題目描述:瑪雅人有一種密碼,如果字符串中出現連續的2012四個數字就能解開密碼。給一個長度爲N的字符串,(2=<N<=13)該字符串中只含有0,1,2三種數字,問這個字符串要移位幾次才能解開密碼,每次只能移動相鄰的兩個數字。例如02120經過一次移位,可以得到20120,01220,02210,02102,其中20120符合要求,因此輸出爲1.如果無論移位多少次都解不開密碼,輸出-1。【清華大學】

  • 輸入格式:輸入包含多組測試數據,每組測試數據由兩行組成。第一行爲一個整數N,代表字符串的長度(2<=N<=13)。第二行爲一個僅由0、1、2組成的,長度爲N的字符串。
  • 輸出格式:對於每組測試數據,若可以解出密碼,輸出最少的移位次數;否則輸出-1。
  • 樣例輸入:
    • 5
    • 02120
  • 樣例輸出:
    • 1

示例代碼:

#include <iostream>
#include <queue>
#include <map>
#include <string>

using namespace std;

struct Maya{
	string str;
	int moveCount;
	Maya(string s, int m = 0):str(s), moveCount(m){};
};

queue<Maya> myQueue;
map<string, int> myMap;
string inputStr;

bool ContainStr(string str){
	if(str.find("2012") == string::npos){
		return false;
	}
	return true;
}

string swap(string str, int i, int j){
	int tmp = str[i];
	str[i] = str[j];
	str[j] = tmp;
	return str;
}

int BFS(){
	while(!myQueue.empty()){
		Maya maya = myQueue.front();
		if(ContainStr(maya.str)){
			return maya.moveCount;
		}
		myQueue.pop();
		for(int i = 0; i < inputStr.size() - 1; i++){
			Maya newMaya(swap(maya.str, i, i + 1), maya.moveCount + 1);
			if(myMap.find(newMaya.str) == myMap.end()){
				myQueue.push(newMaya);
				myMap[newMaya.str]++;
			}
		}
	}
	return -1;
}

int main(){
	int n;
	while(cin >> n >> inputStr){
		myQueue.push(Maya(inputStr));
		int answer = BFS();
		cout << answer << endl;
		while(!myQueue.empty()){
			myQueue.pop();
		}
		myMap.clear();
	}
	return 0;
}

4、題目描述:The knight is getting bored of seeing the same black and white squares again and again and has decided to make a journey around the world. Whenever a knight moves, it is two squares in one direction and one square perpendicular(垂直線) to this. 【按照日字規則行走】The world of a knight is the chessboard he is living on. Our knight lives on a chessboard that has a smaller area than a regular 8 * 8 board, but it is still rectangular. Can you help this adventurous knight to make travel plans?
Find a path such that the knight visits every square once. The knight can start and end on any square of the board.【POJ 2488】

  • 輸入格式:The input begins with a positive integer n in the first line. The following lines contain n test cases. Each test case consists of a single line with two positive integers p and q, such that 1 <= p * q <= 26. This represents a p * q chessboard, where p describes how many different square numbers 1, . . . , p exist, q describes how many different square letters exist. These are the first q letters of the Latin alphabet: A, . . .
  • 輸出格式:The output for every scenario(方案) begins with a line containing "Scenario #i:", where i is the number of the scenario starting at 1. Then print a single line containing the lexicographically(字典序) first path that visits all squares of the chessboard with knight moves followed by an empty line. The path should be given on a single line by concatenating(使連接) the names of the visited squares. Each square name consists of a capital letter followed by a number.If no such path exist, you should output impossible on a single line.
  • 樣例輸入:
    • 3
    • 1 1
    • 2 3
    • 4 3
  • 樣例輸出:
    • Scenario #1:
    • A1
    •  
    • Scenario #2:
    • impossible
    •  
    • Scenario #3:
    • A1B3C1A2B4C2A3B1C3A4B2C4
    •  

示例代碼:

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>

using namespace std;

const int MAXN = 29;

int visit[MAXN][MAXN];
int p, q;
int directions[8][2] = {
	{-1, -2}, {1, -2}, {-2, -1}, {2, -1},
	{-2, 1}, {2, 1}, {-1, 2}, {1, 2}
};

bool DFS(int x, int y, int step, string answer){
	if(step == p * q){
		cout << answer << endl << endl;
		return true;
	}else{
		for(int i = 0; i < 8; i++){
			int nx =  x + directions[i][0];
			int ny = y + directions[i][1];
			char col = ny + 'A';
			char row = nx + '1';
			if(nx < 0 || ny < 0 || nx >= p || ny >= q || visit[nx][ny] == 1){
				continue;
			}
			visit[nx][ny] = 1;
			if(DFS(nx, ny, step + 1, answer + col + row)){
				return true;
			}
			visit[nx][ny] = 0;
		}
	}
	return false;
}

int main(){
	int number;
	while(cin >> number){
		for(int i = 1; i <= number; i++){
			memset(visit, 0, sizeof(visit));
			cin >> p >> q;
			cout << "Scenario #" << i << ":" << endl;
			visit[0][0] = 1;
			if(!DFS(0, 0, 1, "A1")){
				cout << "impossible" << endl << endl;
			}
		}
	}
	return 0;
}

5、題目描述:Given a set of sticks of various lengths, is it possible to join them end-to-end to form a square?【University of Waterloo Local Contest 2002.09.21】

  • 輸入格式:The first line of input contains N, the number of test cases. Each test case begins with an integer 4 <= M <= 20, the number of sticks. M integers follow; each gives the length of a stick - an integer between 1 and 10,000.
  • 輸出格式:For each case, output a line containing "yes" if is is possible to form a square; otherwise output "no".
  • 樣例輸入:
    • 3
    • 4 1 1 1 1
    • 5 10 20 30 40 50
    • 8 1 7 2 6 4 4 3 5
  • 樣例輸出:
    • yes
    • no
    • yes

示例代碼:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;

const int MAXN = 21;

int m, side; //m爲火柴數目,side爲正方形邊長
int myVector[MAXN];
int hasVisited[MAXN];

bool compareDesc(const int a, const int b){
	return a > b;
}

bool DFS(int sum, int num, int pos){
	if(num == 3){
		return true;
	}else{
		int sample = 0;//遇到sample長度的火柴可以跳過
		for(int i = pos; i < m; i++){
			if(sum + myVector[i] > side || hasVisited[i] == 1 || myVector[i] == sample){
				continue;
			}
			hasVisited[i] = 1;
			if(sum + myVector[i] == side){
				if(DFS(0, num + 1, 0)){
					return true;
				}else{
					sample = myVector[i];
				}
			}else{
				if(DFS(sum + myVector[i], num, pos + 1)){
					return true;
				}else{
					sample = myVector[i];
				}
			}
			hasVisited[i] = 0;
		}
	}
	return false;
}

int main(){
	int caseNumber;
	while(cin >> caseNumber){
		for(int i = 0; i < caseNumber; i++){
			cin >> m;
			int sum = 0;
			for(int i = 0; i < m; i++){
				cin >> myVector[i];
				sum += myVector[i];
			}
			memset(hasVisited, 0, sizeof(hasVisited));
			side = sum / 4;
			int flag = true;
			sort(myVector, myVector + m, compareDesc);
			if(myVector[0] > side){
				flag = false;
			}else{
				if(!DFS(0, 0, 0)){
					flag = false;
				}								
			}
			if(flag){
				cout << "yes" << endl;
			}else{
				cout << "no" << endl;
			}
		}
	}
	return 0;
}

/**
* 11		
* 7 2 2
* 3 3 3 2
* 3 2 2 2
* 3 2 2 2
* else中的剪枝避免多次算3
*/

6、題目描述:有一個神奇的口袋,總的容積是40,用這個口袋可以變出一些物品,這些物品的總體積必須是40。John現在有n個想要得到的物品,每個物品的體積分別是a1,a2……an。John可以從這些物品中選擇一些,如果選出的物體的總體積是40,那麼利用這個神奇的口袋,John就可以得到這些物品。現在的問題是,John有多少種不同的選擇物品的方式。【北京大學】

  • 輸入格式:輸入的第一行是正整數n (1 <= n <= 20),表示不同的物品的數目。接下來的n行,每行有一個1到40之間的正整數,分別給出a1,a2……an的值。
  • 輸出格式:輸出不同的選擇物品的方式的數目。
  • 樣例輸入:
    • 3
    • 20
    • 20
    • 20
  • 樣例輸出:
    • 3

示例代碼1:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int TOTAL_WEIGHT = 40;

vector<int> goods;

int count(int sum, int loc){
	if(sum == TOTAL_WEIGHT){
		return 1;
	}
	if(loc == goods.size() || sum > TOTAL_WEIGHT){
		return 0;
	}
	return count(sum + goods[loc], loc + 1) + count(sum, loc + 1);
}

int main(){
	int inputNumber, n;
	while(cin >> n){
		for(int i = 0; i < n; i++){
			cin >> inputNumber;
			goods.push_back(inputNumber);
		}
		int result = count(0, 0);
		cout << result << endl;
		goods.clear();
	}
	return 0;
}

示例代碼2:

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 40;

int dp[MAX_N];
int weight[21];

int main(){
	int n;
	while(cin >> n){
		memset(dp, 0, sizeof(dp));
		for(int i = 1; i <= n; i++){
			cin >> weight[i];
			for(int j = MAX_N; j >= weight[i]; j--){
				dp[j] += dp[j - weight[i]];
			}
			dp[weight[i]]++;
		}
		cout << dp[MAX_N] << endl;
	}
	return 0;
}

7、題目描述:會下國際象棋的人都很清楚:皇后可以在橫、豎、斜線上不限步數地喫掉其他棋子。如何將8個皇后放在棋盤上(有8 * 8個方格),使它們誰也不能被喫掉!這就是著名的八皇后問題。 對於某個滿足要求的8皇后的擺放方法,定義一個皇后串a與之對應,即a=b1b2...b8,其中bi爲相應擺法中第i行皇后所處的列數。已經知道8皇后問題一共有92組解(即92個不同的皇后串)。 給出一個數b,要求輸出第b個串。串的比較是這樣的:皇后串x置於皇后串y之前,當且僅當將x視爲整數時比y小。【北京大學】

  • 輸入格式:每組測試數據佔1行,包括一個正整數b(1 <= b <= 92)
  • 輸出格式:輸出有n行,每行輸出對應一個輸入。輸出應是一個正整數,是對應於b的皇后串。
  • 樣例輸入:
    • 2
    • 1
    • 92
  • 樣例輸出:
    • 15863724
    • 84136275

示例代碼:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

vector<string> result;
int queen[9];//第i行的皇后所在的列

void DFS(int row, string answer){
	if(row == 9){
		result.push_back(answer);
		return;
	}
	for(int i = 1; i <= 8; i++){
		queen[row] = i;
		bool flag = true;
		for(int j = 1; j < row; j++){
			//在同一列 || 在主對角線 || 在副對角線
			if(queen[j] == queen[row] || row - j == queen[row] - queen[j] || row + queen[row] == j + queen[j]){
				flag = false;
				break;
			}
		}
		if(flag){
			char c = i + '0';
			DFS(row + 1, answer + c);
		}
	}
	return;
}

int main(){
	DFS(1, "");
	int n;
	while(cin >> n){
		cout << result[n - 1] << endl;
	}
	return 0;
}

參考文獻:

[1]Thomas.H.Cormen Charles E. Leiseron、Ronald L. Rivest Clifford Srein. 算法導論(第3版). [M]北京:機械工業出版社,2013.01;
[2]楊澤邦、趙霖. 計算機考研——機試指南(第2版). [M]北京:電子工業出版社,2019.11;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章