算法基础知识——搜索

算法基础知识——搜索

目录:

  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;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值