算法基礎知識——搜索
目錄:
- 基礎知識
- 廣度優先搜索(求解最優值)
- 深度優先搜索(判斷解存在)
- 應用實例
- 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;