算法基础知识——搜索
目录:
- 基础知识
- 广度优先搜索(求解最优值)
- 深度优先搜索(判断解存在)
- 应用实例
- Catch That Cow【HDOJ 2717】
- Find The Multiple【POJ 1426】
- 玛雅人的密码【清华大学】
- A Knight's Journey【POJ 2488】
- Square【University of Waterloo Local Contest 2002.09.21】
- 神奇的口袋【北京大学】
- 八皇后【北京大学】
一、基础知识
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;