BFS算法与树的层次遍历很像,具有明显的层次性,一般都是使用队列来实现的!!!
常用步骤:
1、设置访问标记int visited[N],要覆盖所有的可能访问数据个数,这里设置成int而不是bool,基于一个考虑,多次循环时不用每次都清空visited,传递进去每次一个数字即可,比如第一次标记为1,判断也采用==1,之后递加即可。
2、设置一个node,用来记录相关参数和当前的步数,比如:
struct node { int i; int j; int k; int s;//步数 };
3、设计数据读取过程
int count=1; while(true) { cin>>*****; if(l==0 && r==0 && c==0)//跳出机制 break; for(i=0;i<l;i++)//读数据 { for(j=0;j<r;j++) cin>>road[i][j]; getline(cin,tmp); } getroad(count);//传递进去次数,用来visited的标记 count++;//每次递增等等 }
4、设计队列是否为空的while循环
while(!q.empty()) { first=q.front(); q.pop(); ...... ...... ...... }
第一道题目:Catch That Cow http://poj.org/problem?id=3278
Input
Output
Sample Input
5 17
Sample Output
4
先对知识点进行初步总结:
搜索算法主要包括DFS和BFS以及这两种方式的变种,另外搜索算法还可以根据搜索条件的信息是否为启发式的信息分为盲目搜索和启发式搜索等。
与深度搜索和广度搜索关联最大的知识点是树与二叉树遍历相关,另外在搜索过程中一定注意搜索条件的剪枝!!
剪枝:就是在搜索算法的优化中,通过某种判断,避免一些不必要的遍历过程。剪枝优化的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。
在设计过程中需要通过设计出合理的判断方法,以决定某一分支的取舍,需要遵循一定的原则:
1) 正确性
剪枝的前提是一定要保证不丢失正确的结果。
2)准确性
在保证了正确性的基础上,需要根据具体问题具体分析,采用合适的判断手段,使不包含最优解的枝条尽可能多的被剪去,以达到程序“最优化”的目的。
3)高效性
优化的根本目的是要减少搜索的次数,使程序运行的时间减少。一定要在优化与效率之间寻找一个平衡点,使得程序的时间复杂度尽可能降低。
剪枝算法按照其判断思路可大致分成两类:可行性剪枝及最优性剪枝:
可行性剪枝:判断继续搜索能否得出答案,如果不能直接回溯。
最优性剪枝:又称为上下界剪枝,是一种重要的搜索剪枝策略。它记录当前得到的最优值,如果当前结点已经无法产生比当前最优解更优的解时,可以提前回溯。
参考自这里
所以对于剪枝的设计是保证一些搜索算法不超时的重要设计步骤!!!
回到题目,此题使用全搜索肯定会超时,所以一定要根据需求设计剪枝条件:
1、x<0时肯定需要剪枝,因为此时不可能再求得最优解;
2、x最大值的剪枝是需要确定为多少呢?有人说是要2*N,但是直接使用N也是可以AC的,所以尽量使用N吧。
参考代码:
1 #include <iostream> 2 #include <cstdio> 3 #include<queue> 4 using namespace std; 5 6 #define N 100000 7 int map[N+1]; 8 9 int n,k; 10 11 struct node 12 { 13 int num; 14 int step; 15 }; 16 17 bool check(int local_num) 18 { 19 if(local_num <= N && local_num > 0 && map[local_num] == 0) 20 return true; 21 else 22 return false; 23 } 24 25 26 void bfs() 27 { 28 queue<node> Q; 29 node first,next; 30 first.num = n; 31 first.step = 0; 32 map[first.num] = 1; 33 34 if(n == k) 35 { 36 cout<<first.step; 37 return; 38 } 39 Q.push(first); 40 41 while(!Q.empty()) 42 { 43 first = Q.front(); 44 Q.pop(); 45 46 next.step = first.step + 1; 47 48 next.num = first.num - 1; 49 if(next.num == k) 50 { 51 cout<<next.step; 52 return; 53 } 54 if(check(next.num)) 55 { 56 map[next.num] = 1; 57 Q.push(next); 58 } 59 60 61 next.num = first.num + 1; 62 if(next.num == k) 63 { 64 cout<<next.step; 65 return; 66 } 67 if(check(next.num)) 68 { 69 map[next.num] = 1; 70 Q.push(next); 71 } 72 73 next.num = 2 * first.num; 74 if(next.num == k) 75 { 76 cout<<next.step; 77 return; 78 } 79 if(check(next.num)) 80 { 81 map[next.num] = 1; 82 Q.push(next); 83 } 84 } 85 return; 86 } 87 88 int main() 89 { 90 while(cin>>n>>k) 91 bfs(); 92 93 return 0; 94 }
另外的一个考虑,当数据量比较多时,直接使用队列应该是不如数组模拟更节省内存。对比一下:
直接使用标准库的queue | Memory: 1248K Time: 16MS |
使用数组来模拟循环队列 | Memory: 1868K Time: 32MS |
却得到了更为耗时的结果,也就说明标准库的设计肯定是比较优化的结果了,一定要尽量使用标准库的数据结构,而不是自己创造!!!
使用数组模拟循环队列代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 using namespace std; 5 6 #define N 100000 7 8 int n,k; 9 10 struct node 11 { 12 int num; 13 int step; 14 }; 15 16 int map[N+1]; 17 node queue1[N+1]; 18 19 20 bool check(int local_num) 21 { 22 if(local_num <= N && local_num > 0 && map[local_num] == 0) 23 return true; 24 else 25 return false; 26 } 27 28 29 void bfs() 30 { 31 node first,next; 32 first.num = n; 33 first.step = 0; 34 map[first.num] = 1; 35 36 if(n == k) 37 { 38 cout<<first.step; 39 return; 40 } 41 42 int f=0,l=0; 43 44 45 46 queue1[f++] = first; 47 48 while(f != l) 49 { 50 first = queue1[l++]; 51 52 next.step = first.step + 1; 53 54 next.num = first.num - 1; 55 if(next.num == k) 56 { 57 cout<<next.step; 58 return; 59 } 60 if(check(next.num)) 61 { 62 map[next.num] = 1; 63 queue1[f++] = next; 64 } 65 66 67 next.num = first.num + 1; 68 if(next.num == k) 69 { 70 cout<<next.step; 71 return; 72 } 73 if(check(next.num)) 74 { 75 map[next.num] = 1; 76 queue1[f++] = next; 77 } 78 79 next.num = 2 * first.num; 80 if(next.num == k) 81 { 82 cout<<next.step; 83 return; 84 } 85 if(check(next.num)) 86 { 87 map[next.num] = 1; 88 queue1[f++] = next; 89 } 90 } 91 return; 92 } 93 94 int main() 95 { 96 while(cin>>n>>k) 97 bfs(); 98 99 return 0; 100 }
New one: Find The Multiple: http://poj.org/problem?id=1426
Input
Output
Sample Input
2 6 19 0
Sample Output
10 100100100100100100 111111111111111111
题目是一个典型的逆向广度优先组合0和1组成的数据,判断是否可以被n整除即可。
本题最大的考点除此之外还有对整数数据结构的考察,即short--int--long--long long--unsigned long long的所能表示的最大范围!
总结:
占用内存字节 表示数据位数
char -128 ~ +127 (1 Byte)
short -32767 ~ + 32768 (2 Bytes)
unsigned short 0 ~ 65536 (2 Bytes)
int -2147483648 ~ +2147483647 (4 Bytes) 11位,超过11位的数据一定考虑long long 和 unsigned long long
unsigned int 0 ~ 4294967295 (4 Bytes)
long == int
long long -9223372036854775808 ~ +9223372036854775807 (8 Bytes)
unsigned long long的最大值:0~1844674407370955161 (8 Bytes)
double 1.7 * 10^308 (8 Bytes)
参考代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 using namespace std; 5 6 #define N 1000 7 8 void bfs(int n) 9 { 10 if(n == 1) 11 { 12 cout<<"1"<<endl; 13 return; 14 } 15 16 queue<unsigned long long> Q; 17 18 unsigned long long first = 1; 19 Q.push(first); 20 21 while(1) 22 { 23 first = Q.front(); 24 Q.pop(); 25 26 first = first*10; 27 28 if(first%n == 0) 29 { 30 cout<<first<<endl; 31 break; 32 } 33 Q.push(first); 34 35 first += 1; 36 if(first%n == 0) 37 { 38 cout<<first<<endl; 39 break; 40 } 41 Q.push(first); 42 } 43 44 return; 45 } 46 47 int main() 48 { 49 // freopen("data.in", "r", stdin); 50 51 52 int n; 53 while(cin>>n) 54 { 55 if(n == 0) 56 break; 57 bfs(n); 58 } 59 60 //fclose(stdin); 61 return 0; 62 }
New one: Prime Path: http://poj.org/problem?id=3126
Input
Output
Sample Input
3 1033 8179 1373 8017 1033 1033
Sample Output
6 7 0
需要明确:
1、素数定义
质数(prime number)又称素数,有无限个。质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数的数称为质数。
质数大于等于2 不能被它本身和1以外的数整除,即对正整数n,如果用2到 根号n 之间的所有整数去除,均无法整除,则n为质数。
判断:
python:
1 def is_prime(n): 2 list_num = [] 3 for i in range(2, n): 4 for num in range(2, int(sqrt(n))+1): 5 if i % num == 0 and i != num: 6 break 7 elif i % num != 0 and num == int(sqrt(n)): 8 list_num.append(i) 9 return list_num
C++:
1 bool isPrime(unsigned long n) 2 { 3 if (n <= 3) 4 return n > 1; 5 else if (n % 2 == 0 || n % 3 == 0) 6 return false; 7 else 8 { 9 for (unsigned short i = 5; i * i <= n; i += 6)//这里的等号一定不能少!!!!! 10 if (n % i == 0 || n % (i + 2) == 0) 11 return false; 12 return true; 13 } 14 }
解决了这些问题,题目就很好理解了。
参考代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 #include <cmath> 5 #include <cstring> 6 7 using namespace std; 8 9 int num_map[9000];//n-1000为索引值 10 11 struct node 12 { 13 int num; 14 int step; 15 }; 16 17 bool isPrime(int n) 18 { 19 //考虑2,3,5,...到根号下n能否被整除 20 if(num_map[n-1000]) 21 { 22 num_map[n-1000] = 1; 23 return false; 24 } 25 26 if (n % 2 == 0 || n % 3 == 0) 27 return false; 28 else 29 { 30 for (unsigned short i = 5; i * i <= n; i += 6) 31 if (n % i == 0 || n % (i + 2) == 0) 32 return false; 33 return true; 34 } 35 } 36 37 38 void bfs(int lnum1,int lnum2) 39 { 40 if(lnum1 == lnum2) 41 { 42 cout<<"0"<<endl; 43 return; 44 } 45 queue<node> Q; 46 node n1,n2; 47 n1.num = lnum1; 48 n1.step = 0; 49 50 if(isPrime(n1.num)) 51 Q.push(n1); 52 53 while(!Q.empty()) 54 { 55 n1 = Q.front(); 56 Q.pop(); 57 58 for(int i=0;i<4;i++) 59 { 60 int es = 1; 61 for(int ei=0;ei<3-i;ei++)//根据i值获得处理的对应位数 62 es *= 10; 63 64 for(int j=0;j<10;j++) 65 { 66 if(i==0 && j==0) 67 continue; 68 if(i==3) 69 j++; 70 71 int tmpj1 = n1.num/es/10; 72 73 int tmpj2= n1.num%es; 74 75 int tmp = tmpj1*es*10 + j*es + tmpj2; 76 77 if(tmp == n1.num) 78 continue; 79 80 n2.num = tmp; 81 n2.step = n1.step + 1; 82 if(n2.num == lnum2) 83 { 84 cout<<n2.step<<endl; 85 return; 86 } 87 88 if(isPrime(tmp)) 89 Q.push(n2); 90 } 91 } 92 } 93 return; 94 } 95 96 int main() 97 { 98 int n,num1,num2; 99 cin>>n; 100 101 while(n>0 && cin>>num1>>num2) 102 { 103 bfs(num1,num2); 104 n--; 105 } 106 107 return 0; 108 }
此代码可以得出正确的结果,但是会超时Time Limit Exceeded,原因在于每一次的操作都有这么多次的乘法除法和取余的操作,尤其是判断一个数是不是质数的取余判断也是非常的多。
所以接下来要做的地方有两点:1、优化质数判断算法,2、用空间换时间来避免超时
1、判断1~n中所有的质数
从2~N逐一检查,如果是就显示出来,如果不是,就检查下一个。方法正确但效率不高。改进1就是只测试2与所有的奇数就足够了,同理,3是质数,但3的倍数却不是,如果能够把2与3的倍数跳过去而不测试,任意连续的6个数中,只测试2个。以6n,6n+1,6n+2,6n+3,6n+4,6n+5为例,6n,6n+2,6n+4是偶数,又6n+3是3的倍数,所以如果2与3的倍数都不理会,只要测试的数就只留下6n+1和6n+5而已了,因而工作量只是前面想法的2/6=1/3。
另外判断一个数i是不是质数,是使用2~sqrt(i)的数去除,没有能够除尽的则i为质数,否则就不是质数! 这里应该考虑到,如果2除不尽,那么2的倍数也除不尽;同理,3除不尽,3的倍数也除不尽,所以最理想的方法就是用质数去除i来判断。如果是从2开始找所有的质数,则可以准备一个数组prime[],用来存放找到的素数,一开始它里面有2、3、5,判断时用prime[]中小于sqrt(i)的数去除i 即可。
对应题目:判断1000~9999之间的数字是否为质数,只需要记录2-102之间的所有的质数作为判断的基数,然后找到所有的1000-9999中所有的质数标记。然后在大逻辑中直接进行判断即可。
注意点:
1、判断基数设置到102,如果代码中只取i<100,那么会得到25个质数,即p[24]=97,而97*97=9409,那么到9999之间的数据判断就会显示索引越界!使用102则会有26个质数,最后一个为101,而101*101>9999的,正常!!
2、判断质数时使用0-sqrt(n)来除,使用代码prime[j]*prime[j]<=n,这里的等号一定不能省略,否则会得出错误结果!!!!!(此bug调试耗时半天~~~切记)
3、切记广度优先和深度优先的特点:如果只要求最小的步骤数,不要求每一步的详细步骤,则肯定是广度优先,切记广度优先无法输出详细步骤,若要输出每一步的步骤,则肯定是要用深度优先了!!!!
参考代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 #include <cmath> 5 #include <cstring> 6 7 using namespace std; 8 9 int prime[102];//记录0-99所有的质数 10 11 int num_map[9000];//n-1000为索引值 12 13 int num_flag[9000];//n-1000为索引值,标记所有的质数 14 15 struct node 16 { 17 int num; 18 int step; 19 }; 20 21 void init() 22 { 23 prime[0]=2; 24 prime[1]=3; 25 prime[2]=5; 26 27 int i,j,n,pos; 28 bool flag; 29 for(i=3,n=7,pos=4;i<102&&n<102;pos=6-pos) 30 { 31 flag = true; 32 for(j=0;j<i&&prime[j]*prime[j]<=n;j++) 33 { 34 if(n%prime[j] == 0) 35 { 36 flag = false; 37 break; 38 } 39 } 40 if(flag)//n是质数,然后进行下一个数的判断 41 prime[i++]=n; 42 n+=pos; 43 } 44 for(n=1001,pos=2;n<10000;pos=6-pos) 45 { 46 flag=true; 47 for(j=0;prime[j]*prime[j]<=n;j++) 48 { 49 if(n%prime[j] == 0) 50 { 51 flag = false; 52 break; 53 } 54 } 55 if(flag) 56 num_flag[n-1000]=1; 57 n+=pos; 58 } 59 } 60 61 62 void bfs(int is,int lnum1,int lnum2) 63 { 64 if(lnum1 == lnum2) 65 { 66 cout<<"0"<<endl; 67 return; 68 } 69 queue<node> Q; 70 node n1,n2; 71 n1.num = lnum1; 72 n1.step = 0; 73 74 Q.push(n1); 75 num_map[n1.num-1000]=is; 76 77 while(!Q.empty()) 78 { 79 n1 = Q.front(); 80 Q.pop(); 81 82 for(int i=0;i<4;i++) 83 { 84 int es = 1; 85 for(int ei=0;ei<3-i;ei++)//根据i值获得处理的对应位数 86 es *= 10; 87 88 for(int j=0;j<10;j++) 89 { 90 if(i==0 && j==0) 91 continue; 92 if(i==3) 93 j++; 94 95 int tmpj1 = n1.num/es/10; 96 int tmpj2 = n1.num%es; 97 int tmp = tmpj1*es*10 + j*es + tmpj2; 98 99 if(tmp == n1.num || num_map[tmp-1000]==is) 100 continue; 101 102 n2.num = tmp; 103 n2.step = n1.step + 1; 104 if(n2.num == lnum2) 105 { 106 cout<<n2.step<<endl; 107 return; 108 } 109 110 if(num_flag[tmp-1000]) 111 { 112 Q.push(n2); 113 num_map[tmp-1000]=is; 114 } 115 } 116 } 117 } 118 cout<<"Impossible"<<endl; 119 return; 120 } 121 122 int main() 123 { 124 int n,num1,num2; 125 cin>>n; 126 127 init(); 128 while(n>0 && cin>>num1>>num2) 129 { 130 bfs(5-n,num1,num2); 131 n--; 132 } 133 134 return 0; 135 }
New one: Shuffle'm Up: http://poj.org/problem?id=3087
Input
The first line of input contains a single integer N, (1 ≤ N ≤ 1000) which is the number of datasets that follow.
Each dataset consists of four lines of input. The first line of a dataset specifies an integer C, (1 ≤ C ≤ 100) which is the number of chips in each initial stack (S1 and S2). The second line of each dataset specifies the colors of each of the C chips in stack S1, starting with the bottommost chip. The third line of each dataset specifies the colors of each of the C chips in stack S2 starting with the bottommost chip. Colors are expressed as a single uppercase letter (A through H). There are no blanks or separators between the chip colors. The fourth line of each dataset contains 2 * C uppercase letters (A through H), representing the colors of the desired result of the shuffling of S1 and S2 zero or more times. The bottommost chip’s color is specified first.
Output
Output for each dataset consists of a single line that displays the dataset number (1 though N), a space, and an integer value which is the minimum number of shuffle operations required to get the desired resultant stack. If the desired result can not be reached using the input for the dataset, display the value negative 1 (−1) for the number of shuffle operations.
Sample Input
2 4 AHAH HAHA HHAAAAHH 3 CDE CDE EEDDCC
Sample Output
1 2 2 -1
好多遍才读明白,s2和s1交叉叠放,再中间切开,下面为s1,上面为s2,再次进行交叉,依次进行直到得到s12的排放输出循环的步数,如果一直都无法得到s12,则输出-1。
那么什么时候标志着肯定无法模拟出来呢?切开后的s1与原始的s1一样并且s2和原始的s2也一致,那么肯定会进入死循环!!!
使用字符串进行模拟是个很好的方法,一直模拟下去,直到成功或者进入死循环输出-1即可。
参考代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 #include <cmath> 5 #include <cstring> 6 7 using namespace std; 8 string s1,s2,s0;//把这三个变量当作全局的,方便比对 9 10 void shuffle(int len) 11 { 12 string ns1,ns2,ns0; 13 int step=0; 14 ns1=s1; 15 ns2=s2; 16 17 while(true) 18 { 19 ns0=""; 20 int i; 21 for(i=0;i<len;i++) 22 { 23 ns0+=ns2[i]; 24 ns0+=ns1[i]; 25 } 26 step+=1; 27 28 if(ns0 == s0) 29 { 30 cout<<step<<endl; 31 return; 32 } 33 ns1=""; 34 ns2=""; 35 for(i=0;i<len;i++) 36 ns1+=ns0[i]; 37 for(i=len;i<2*len;i++) 38 ns2+=ns0[i]; 39 40 if(ns1==s1 && ns2==s2) 41 { 42 cout<<"-1"<<endl; 43 return; 44 } 45 } 46 } 47 48 49 int main() 50 { 51 int n; 52 cin>>n; 53 int i=1; 54 while(n>0) 55 { 56 int len; 57 cin>>len; 58 cin>>s1>>s2>>s0; 59 60 cout<<i++<<" "; 61 shuffle(len); 62 63 n--; 64 } 65 66 return 0; 67 }
直接使用模拟来做,与搜索无关呀!可否使用广度搜索来进行???
New one: Pots: http://poj.org/problem?id=3414
Input
On the first and only line are the numbers A, B, and C. These are all integers in the range from 1 to 100 and C≤max(A,B).
Output
The first line of the output must contain the length of the sequence of operations K. The following K lines must each describe one operation. If there are several sequences of minimal length, output any one of them. If the desired result can’t be achieved, the first and only line of the file must contain the word ‘impossible’.
Sample Input
3 5 4
Sample Output
6 FILL(2) POUR(2,1) DROP(1) POUR(2,1) FILL(2) POUR(2,1)
倒水,