[ACM训练] 算法初级 之 搜索算法 之 广度优先算法BFS (POJ 3278+1426+3126+3087+3414)
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吧。
参考代码:
- #include <iostream>
- #include <cstdio>
- #include<queue>
- using namespace std;
- #define N 100000
- int map[N+];
- int n,k;
- struct node
- {
- int num;
- int step;
- };
- bool check(int local_num)
- {
- if(local_num <= N && local_num > && map[local_num] == )
- return true;
- else
- return false;
- }
- void bfs()
- {
- queue<node> Q;
- node first,next;
- first.num = n;
- first.step = ;
- map[first.num] = ;
- if(n == k)
- {
- cout<<first.step;
- return;
- }
- Q.push(first);
- while(!Q.empty())
- {
- first = Q.front();
- Q.pop();
- next.step = first.step + ;
- next.num = first.num - ;
- if(next.num == k)
- {
- cout<<next.step;
- return;
- }
- if(check(next.num))
- {
- map[next.num] = ;
- Q.push(next);
- }
- next.num = first.num + ;
- if(next.num == k)
- {
- cout<<next.step;
- return;
- }
- if(check(next.num))
- {
- map[next.num] = ;
- Q.push(next);
- }
- next.num = * first.num;
- if(next.num == k)
- {
- cout<<next.step;
- return;
- }
- if(check(next.num))
- {
- map[next.num] = ;
- Q.push(next);
- }
- }
- return;
- }
- int main()
- {
- while(cin>>n>>k)
- bfs();
- return ;
- }
另外的一个考虑,当数据量比较多时,直接使用队列应该是不如数组模拟更节省内存。对比一下:
直接使用标准库的queue | Memory: 1248K Time: 16MS |
使用数组来模拟循环队列 | Memory: 1868K Time: 32MS |
却得到了更为耗时的结果,也就说明标准库的设计肯定是比较优化的结果了,一定要尽量使用标准库的数据结构,而不是自己创造!!!
使用数组模拟循环队列代码:
- #include <iostream>
- #include <cstdio>
- #include <queue>
- using namespace std;
- #define N 100000
- int n,k;
- struct node
- {
- int num;
- int step;
- };
- int map[N+];
- node queue1[N+];
- bool check(int local_num)
- {
- if(local_num <= N && local_num > && map[local_num] == )
- return true;
- else
- return false;
- }
- void bfs()
- {
- node first,next;
- first.num = n;
- first.step = ;
- map[first.num] = ;
- if(n == k)
- {
- cout<<first.step;
- return;
- }
- int f=,l=;
- queue1[f++] = first;
- while(f != l)
- {
- first = queue1[l++];
- next.step = first.step + ;
- next.num = first.num - ;
- if(next.num == k)
- {
- cout<<next.step;
- return;
- }
- if(check(next.num))
- {
- map[next.num] = ;
- queue1[f++] = next;
- }
- next.num = first.num + ;
- if(next.num == k)
- {
- cout<<next.step;
- return;
- }
- if(check(next.num))
- {
- map[next.num] = ;
- queue1[f++] = next;
- }
- next.num = * first.num;
- if(next.num == k)
- {
- cout<<next.step;
- return;
- }
- if(check(next.num))
- {
- map[next.num] = ;
- queue1[f++] = next;
- }
- }
- return;
- }
- int main()
- {
- while(cin>>n>>k)
- bfs();
- return ;
- }
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)
参考代码:
- #include <iostream>
- #include <cstdio>
- #include <queue>
- using namespace std;
- #define N 1000
- void bfs(int n)
- {
- if(n == )
- {
- cout<<""<<endl;
- return;
- }
- queue<unsigned long long> Q;
- unsigned long long first = ;
- Q.push(first);
- while()
- {
- first = Q.front();
- Q.pop();
- first = first*;
- if(first%n == )
- {
- cout<<first<<endl;
- break;
- }
- Q.push(first);
- first += ;
- if(first%n == )
- {
- cout<<first<<endl;
- break;
- }
- Q.push(first);
- }
- return;
- }
- int main()
- {
- // freopen("data.in", "r", stdin);
- int n;
- while(cin>>n)
- {
- if(n == )
- break;
- bfs(n);
- }
- //fclose(stdin);
- return ;
- }
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:
- def is_prime(n):
- list_num = []
- for i in range(2, n):
- for num in range(2, int(sqrt(n))+1):
- if i % num == 0 and i != num:
- break
- elif i % num != 0 and num == int(sqrt(n)):
- list_num.append(i)
- return list_num
C++:
- bool isPrime(unsigned long n)
- {
- if (n <= )
- return n > ;
- else if (n % == || n % == )
- return false;
- else
- {
- for (unsigned short i = ; i * i <= n; i += )//这里的等号一定不能少!!!!!
- if (n % i == || n % (i + ) == )
- return false;
- return true;
- }
- }
解决了这些问题,题目就很好理解了。
参考代码:
- #include <iostream>
- #include <cstdio>
- #include <queue>
- #include <cmath>
- #include <cstring>
- using namespace std;
- int num_map[];//n-1000为索引值
- struct node
- {
- int num;
- int step;
- };
- bool isPrime(int n)
- {
- //考虑2,3,5,...到根号下n能否被整除
- if(num_map[n-])
- {
- num_map[n-] = ;
- return false;
- }
- if (n % == || n % == )
- return false;
- else
- {
- for (unsigned short i = ; i * i <= n; i += )
- if (n % i == || n % (i + ) == )
- return false;
- return true;
- }
- }
- void bfs(int lnum1,int lnum2)
- {
- if(lnum1 == lnum2)
- {
- cout<<""<<endl;
- return;
- }
- queue<node> Q;
- node n1,n2;
- n1.num = lnum1;
- n1.step = ;
- if(isPrime(n1.num))
- Q.push(n1);
- while(!Q.empty())
- {
- n1 = Q.front();
- Q.pop();
- for(int i=;i<;i++)
- {
- int es = ;
- for(int ei=;ei<-i;ei++)//根据i值获得处理的对应位数
- es *= ;
- for(int j=;j<;j++)
- {
- if(i== && j==)
- continue;
- if(i==)
- j++;
- int tmpj1 = n1.num/es/;
- int tmpj2= n1.num%es;
- int tmp = tmpj1*es* + j*es + tmpj2;
- if(tmp == n1.num)
- continue;
- n2.num = tmp;
- n2.step = n1.step + ;
- if(n2.num == lnum2)
- {
- cout<<n2.step<<endl;
- return;
- }
- if(isPrime(tmp))
- Q.push(n2);
- }
- }
- }
- return;
- }
- int main()
- {
- int n,num1,num2;
- cin>>n;
- while(n> && cin>>num1>>num2)
- {
- bfs(num1,num2);
- n--;
- }
- return ;
- }
此代码可以得出正确的结果,但是会超时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、切记广度优先和深度优先的特点:如果只要求最小的步骤数,不要求每一步的详细步骤,则肯定是广度优先,切记广度优先无法输出详细步骤,若要输出每一步的步骤,则肯定是要用深度优先了!!!!
参考代码:
- #include <iostream>
- #include <cstdio>
- #include <queue>
- #include <cmath>
- #include <cstring>
- using namespace std;
- int prime[];//记录0-99所有的质数
- int num_map[];//n-1000为索引值
- int num_flag[];//n-1000为索引值,标记所有的质数
- struct node
- {
- int num;
- int step;
- };
- void init()
- {
- prime[]=;
- prime[]=;
- prime[]=;
- int i,j,n,pos;
- bool flag;
- for(i=,n=,pos=;i<&&n<;pos=-pos)
- {
- flag = true;
- for(j=;j<i&&prime[j]*prime[j]<=n;j++)
- {
- if(n%prime[j] == )
- {
- flag = false;
- break;
- }
- }
- if(flag)//n是质数,然后进行下一个数的判断
- prime[i++]=n;
- n+=pos;
- }
- for(n=,pos=;n<;pos=-pos)
- {
- flag=true;
- for(j=;prime[j]*prime[j]<=n;j++)
- {
- if(n%prime[j] == )
- {
- flag = false;
- break;
- }
- }
- if(flag)
- num_flag[n-]=;
- n+=pos;
- }
- }
- void bfs(int is,int lnum1,int lnum2)
- {
- if(lnum1 == lnum2)
- {
- cout<<""<<endl;
- return;
- }
- queue<node> Q;
- node n1,n2;
- n1.num = lnum1;
- n1.step = ;
- Q.push(n1);
- num_map[n1.num-]=is;
- while(!Q.empty())
- {
- n1 = Q.front();
- Q.pop();
- for(int i=;i<;i++)
- {
- int es = ;
- for(int ei=;ei<-i;ei++)//根据i值获得处理的对应位数
- es *= ;
- for(int j=;j<;j++)
- {
- if(i== && j==)
- continue;
- if(i==)
- j++;
- int tmpj1 = n1.num/es/;
- int tmpj2 = n1.num%es;
- int tmp = tmpj1*es* + j*es + tmpj2;
- if(tmp == n1.num || num_map[tmp-]==is)
- continue;
- n2.num = tmp;
- n2.step = n1.step + ;
- if(n2.num == lnum2)
- {
- cout<<n2.step<<endl;
- return;
- }
- if(num_flag[tmp-])
- {
- Q.push(n2);
- num_map[tmp-]=is;
- }
- }
- }
- }
- cout<<"Impossible"<<endl;
- return;
- }
- int main()
- {
- int n,num1,num2;
- cin>>n;
- init();
- while(n> && cin>>num1>>num2)
- {
- bfs(-n,num1,num2);
- n--;
- }
- return ;
- }
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即可。
参考代码:
- #include <iostream>
- #include <cstdio>
- #include <queue>
- #include <cmath>
- #include <cstring>
- using namespace std;
- string s1,s2,s0;//把这三个变量当作全局的,方便比对
- void shuffle(int len)
- {
- string ns1,ns2,ns0;
- int step=;
- ns1=s1;
- ns2=s2;
- while(true)
- {
- ns0="";
- int i;
- for(i=;i<len;i++)
- {
- ns0+=ns2[i];
- ns0+=ns1[i];
- }
- step+=;
- if(ns0 == s0)
- {
- cout<<step<<endl;
- return;
- }
- ns1="";
- ns2="";
- for(i=;i<len;i++)
- ns1+=ns0[i];
- for(i=len;i<*len;i++)
- ns2+=ns0[i];
- if(ns1==s1 && ns2==s2)
- {
- cout<<"-1"<<endl;
- return;
- }
- }
- }
- int main()
- {
- int n;
- cin>>n;
- int i=;
- while(n>)
- {
- int len;
- cin>>len;
- cin>>s1>>s2>>s0;
- cout<<i++<<" ";
- shuffle(len);
- n--;
- }
- return ;
- }
直接使用模拟来做,与搜索无关呀!可否使用广度搜索来进行???
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)
倒水,
[ACM训练] 算法初级 之 搜索算法 之 广度优先算法BFS (POJ 3278+1426+3126+3087+3414)的更多相关文章
- [ACM训练] 算法初级 之 搜索算法 之 深度优先算法DFS (POJ 2251+2488+3083+3009+1321)
对于深度优先算法,第一个直观的想法是只要是要求输出最短情况的详细步骤的题目基本上都要使用深度优先来解决.比较常见的题目类型比如寻路等,可以结合相关的经典算法进行分析. 常用步骤: 第一道题目:Dung ...
- 【算法入门】广度/宽度优先搜索(BFS)
广度/宽度优先搜索(BFS) [算法入门] 1.前言 广度优先搜索(也称宽度优先搜索,缩写BFS,以下采用广度来描述)是连通图的一种遍历策略.因为它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较 ...
- 最近关于ACM训练与算法的总结
到了大四以后越来越意识到基础知识的重要性,很多高屋建瓴的观点与想法都是建立在坚实的基础之上的, 招式只有在强劲的内力下才能发挥最大的作用,曾经有段时间我有这样的想法:我们出去以后和其他 ...
- C语言之广度优先算法
广度优先算法又称宽度优先搜索,是一种简便的图的搜索算法之一.搜索方式大致是这样的: 直到搜索到目标结点(结点就是那些圆球球,其中有一个或者多个是目标结点)或者搜完了整个图都没找到目标结点就停止搜索. ...
- 寒假的ACM训练(一)
今天开始ACM训练,选择了刘汝佳的<挑战编程>,暂时算是开始了. 测评的网址: http://www.programming-challenges.com 第一个题目是水题啦.3n+1. ...
- 广度优先搜索 BFS算法
广度优先搜索算法(Breadth-First-Search,BFS),又称作宽度优先搜索.BFS算法是从根节点开始,沿着树的宽度遍历树的节点.如果所有节点均被访问,则算法中止. 算法思想 1.首先将根 ...
- 2014暑假ACM训练总结
2014暑假ACM训练总结报告 匆匆之中,一个暑假又过去了,在学校训练的这段日子真的是感觉日子过得好快啊! 时光如箭,日月如梭! 匆忙的学习之中一个暑假就这样结束了,现在就来写一些总结吧,供自己以后阅 ...
- 指定url和深度的广度优先算法爬虫的python实现
本文参考http://zoulc001.iteye.com/blog/1186996 广度优先算法介绍 整个的广度优先爬虫过程就是从一系列的种子节点开始,把这些网页中的"子节点"( ...
- golang 实现广度优先算法(走迷宫)
maze.go package main import ( "fmt" "os" ) /** * 广度优先算法 */ /** * 从文件中读取数据 */ fun ...
随机推荐
- ng-disabled 不起作用的解决办法
不知道这算不算 Angular.js 的一个bug.但搜索一番后找到了一个变通的解决办法. 业务需求是这样的, 按钮被点击一次之后就设置为禁用状态, 以阻止多次无效的点击.但现在很多框架都用 < ...
- Thread Safety线程安全
Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分 如果disabled就选择nts(php_stomp-1.0.9-5.5-nts-vc11-x86.zi ...
- 如何在Linux上使用文件作为内存交换区(Swap Area)
交换区域(Swap Area)有什么作用? 交换分区是操作系统在内存不足(或内存较低)时的一种补充.通俗的说,如果说内存是汽油,内存条就相当于油箱,交换区域则相当于备用油箱. Ubuntu Linux ...
- (一)Netty源码学习笔记之概念解读
尊重原创,转载注明出处,原文地址:http://www.cnblogs.com/cishengchongyan/p/6121065.html 博主最近在做网络相关的项目,因此有契机学习netty,先 ...
- repo 修改邮箱地址
需要重新运行 repo init 被带上参数: --config-name xx@a.com
- Java注解基础概念总结
注解的概念 注解(Annotation),也叫元数据(Metadata),是Java5的新特性,JDK5引入了Metadata很容易的就能够调用Annotations.注解与类.接口.枚举在同一个层次 ...
- 你不知道的Spring配置文件
Spring配置文件是用于指导Spring工厂进行Bean生产.依赖关系注入(装配)及Bean实例分发的"图纸".Java EE程序员必须学会并灵活应用这份"图纸&quo ...
- ios 输入框bar设置
_textView = [[class alloc] init]; _textView.translatesAutoresizingMaskIntoConstraints = NO; ...
- hive 复杂类型
hive提供一种复合类型的数据 struct:可以使用"."来存取数据 map:可以使用键值对来存取数据 array:array中存取的数据为相同类型,其中的数据可以通过下表获取数 ...
- 高性能缓存系统Redis安装与使用
在互联网后台架构中,需要应付高并发访问数据库,很多时候都会在数据库上层增加一个缓存服务器来保存经常读写的数据以减少数据库压力,可以使用LVS.Memcached或Redis,Memcached和Red ...