搜索涉及的知识点

BFS--双向广搜

DFS-剪枝(可行性剪枝、最优性剪枝、玄学剪枝)

A*

IDA*

迭代加深搜索IDDFS

DLX

记忆化搜索

模拟退火

遗传算法

爬山算法

随机化搜索

 

启发式搜索:启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。

估价函数:从当前节点移动到目标节点的预估费用;这个估计就是启发式的。在寻路问题和迷宫问题中,我们通常用曼哈顿(manhattan)估价函数(下文有介绍)预估费用。

A*算法与BFS:可以这样说,BFS是A*算法的一个特例。对于一个BFS算法,从当前节点扩展出来的每一个节点(如果没有被访问过的话)都要放进队列进行进一步扩展。也就是说BFS的估计函数h永远等于0,没有一点启发式的信息,可以认为BFS是“最烂的”A*算法。

http://www.cppblog.com/mythit/archive/2009/04/19/80492.aspx

选取最小估价:如果学过数据结构的话,应该可以知道,对于每次都要选取最小估价的节点,应该用到最小优先级队列(也叫最小二叉堆)。在C++的STL里有现成的数据结构priority_queue,可以直接使用。当然不要忘了重载自定义节点的比较操作符。

A*算法的特点:A*算法在理论上是时间最优的,但是也有缺点:它的空间增长是指数级别的。

IDA*算法:这种算法被称为迭代加深A*算法,可以有效的解决A*空间增长带来的问题,甚至可以不用到优先级队列。如果要知道详细:google一下。

 

1、打印全排列

//打印全排列 
//首先先排序 
int a[maxn]; 
void perm(int st,int en){
	if(st==ed){
		print();
		return;
	}
	else{
		for(int i=st;i<=en;i++){
			swap(a[st],a[i]);
			perm(st+1,en);
			swap(a[st],a[i]);
		}
	}
}
//竞赛题在一般情况下限时1s,所以元素个数小于11 
//平凡下界:最小的复杂度 

2、子集生成,含有m个元素的子集

//子集生成 
//n个数,有2^n个子集,子集问题用二进制来表示最简单 
void print_subset(int n){
	for(int i=0;i<(1<<n);i++){
		for(int j=0;j<n;j++){
			if(i&(1<<j)) cout<<j<<" ";
		}//注意写法 
		cout<<endl;
	}
}
//如果想要在集合n中打印子集为m的集合
//那就是有m个1,而操作kk&(kk-1) 的含义就是消除最后的1,这样操作多少次,就会有多少个1
void print_sub(int n,int k){
	for(int i=0;i<(1<<n);i++){
		int temp=i,num=0;
		while(temp){
			temp=temp&(temp-1);
			num++;
		}
		if(num==k){
			for(int j=0;j<n;j++){
				if(i&&(1<<j)) cout<<j<<" ";
			}
			cout<<endl;
		}
	}
} 

3、八数码问题

给定一个初始的3*3的棋局和一个目标棋局,输出最少需要多少步到达目标棋局 

八数码的最重要的问题是判重:康托展开
cantor()的作用在于给出一个排列,输出这个第几个排列,这样就可以判重

其他解决方法:

https://www.cnblogs.com/zufezzt/p/5659276.html 

struct node{
	int a[10];
	int ans;  //记录最小步数 
}; 
int dis[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
const  int len=362880   //9!=362880
int vis[len];  //标记这种状态有没有访问过 
int st[10],ed[10] ; //初始、结局状态 

long long fac[]={0,1,2,6,24,120,720,5040,40320,362880}; 
//cantor用到的常数 
bool cantor(int str[],int n){  //判重 
	LL res=0;
	int con=0;
	for(int i=0;i<n;i++){
		con=0;
		for(int j=i+1;j<n;j++){
			if(str[j]<str[i]) con++;
		}
		res+=con*fac[n-i-1];
	}
	if(vis[res]==0){
		vis[res]=1;
		return 1;
	}
	return 0;
}
queue<node> q;
int bfs(){
	node head;
	memcpy(head.a,st,sizeof(head.a)); //注意这个的用法
	head.ans=0;
	
	q.push(head);
	cantor(st,9); //对起点进行vis[]=1
	while(!q.empty()){
		head=q.front();
		q.pop();
		int op;
		for(int i=0;i<9;i++){
			if(head.a[i]==0) {
				op=i;break;
			}
		}
		int x=op%3,y=op/3; //空格的坐标 
		for(int i=0;i<4;i++){
			int xx=x+dis[i][0],yy=y+dis[i][1];
			int newop=xx+yy*3; //转化为一维
			if(xx>=0&&xx<3&&yy>=0&&yy<3){
				node newnode;
				memcpy(&newnode,&head,sizeof(struct node));
				swap(newnode.a[newop],newnode.a[op]);
				newnode.ans++;
				if(memcmp(newnode.a,en,sizeof(en))==0) return newnode.ans;
				if(cantor(newnode.a,9)){
					q.push(newnode);
				}
			} 
		} 
	} 
	return -1;
}

判断能不能到达

//直接判断能不能到达:
//一个状态表示成一维的形式,求出除0之外所有数字的逆序数之和,也就是每个数字前面比它大的数字的个数的和,称为这个状态的逆序。
//若两个状态的逆序奇偶性相同,则可相互到达,否则不可相互到达。
int sum=0;
for(int i=0;t[i];i++){
    if(t[i]=='x') continue;
    for(int j=0;j<i;j++){
        if(t[j]=='x') continue;
        if(t[i]<t[j]) sum++;
        }
    }
    if(sum%2==1) { 
	printf("unsolvable\n");
	 continue; }

记录路径的方法

。。。

康托展开(逆展开),以及相关的优化算法摸鱼日记1:康托展开/逆康托展开 - Slithery - 博客园 (cnblogs.com)

下面是原始的没有优化的康托展开和逆展开

#include<bits/stdc++.h>
using namespace std;
int fac[20],num[20];
int cantor(int per[],int len){
	int rk=0;
	for(int i=0;i<len;i++){
		int x=0;
		for(int j=i+1;j<len;j++)
			if(per[i]>per[j]) x++;
		rk+=x*fac[len-i-1];
	}
	return rk+1;
}
vector<int>incantor(int rk,int len){
	rk--;
	int x;
	vector<int> vec,ans;
	for(int i=1;i<=len;i++) vec.push_back(i);
	for(int i=1;i<=len;i++){
		x=rk/fac[len-i];
		ans.push_back(vec[x]);
		vec.erase(vec.begin()+x);
		rk%=fac[len-i];  //不用-1 
	}
	return ans; 
} 
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>num[i];
	}
	fac[0]=fac[1]=1;
	for(int i=2;i<=n;i++) fac[i]=fac[i-1]*i;
	int rk=cantor(num,n);
	cout<<"rank: "<<rk<<endl;
	if(rk!=1){
		vector<int> v=incantor(rk,n);
		cout<<"permutation: ";
		for(int i=0;i<n;i++) cout<<v[i]<<" ";
		cout<<endl;
	}
	return 0;
}

  

 

4、A*

启发式搜索的一种,其实就是BFS+贪心,给每个状态一个评估函数

//写一个A*算法:其实就是在队列里面,每次都选择其附加函数F最小的,这就是优先队列的应用
//但是注意要重载小于符
//整体难度不大,但是要理解算法
struct node{
	int x,y,step;
	int g;   //实际的距离 
	int h;  //启发的信息 
	int f; //总的估值函数
	bool operator < (const node& a)const{
		return f>a.f;  //注意是反的,其实是小的排前面,不要忘了基础知识 
	} 
}k; 
bool vis[8][8];
int x1,yy1,x2,y2,ans;
int dirs[8][2]={{-2,-1},{-2,1},{2,-1},{2,1},{-1,-2},{-1,2},{1,-2},{1,2}};//8个移动方向
priority_queue<node> q;
bool judge(const node &a){
	if(a.x<0||a.y<0||a.x>=8||a.y>=8) return 0;
	return 1;
}
int manha(const node &a){
	return (abs(a.x-x2)+abs(a.y-y2))*10;  //?
}
void astar(){
	node t,s;
	while(!q.empty()){
		t=q.top();
		q.pop();
		vis[t.x][t.y]=1;
		if(t.x==x2&&t.y==y2){
			ans=t.step;
			return;
		}
		for(int i=0;i<8;i++){
			s.x=t.x+dirs[i][0];
			s.y=t.y+dirs[i][1];
			if(judge(s)&&!vis[s.x][s.y]){
				s.g=t.g+23;//23表示根号5乘以10再取其ceil
				s.h=manha(s);
				s.f=s.g+s.h;
				s.step=t.step+1;
				q.push(s);
			}
	}
	}
}
char line[5];
int main(){
	while(gets(line)){
		x1=line[0]-'a',yy1=line[1]-'1',x2=line[3]-'a',y2=line[4]-'1';
		memset(vis,0,sizeof(vis));
		k.x=x1;
		k.y=yy1;
		k.step=0;k.g=0;
		k.h=manha(k);
		k.f=k.h+k.g;
		while(!q.empty()) q.pop();
		q.push(k);
		astar();
		printf("To get from %c%c to %c%c takes %d knight moves.\n",line[0],line[1],line[3],line[4],ans);
	}
return 0;
}

  

 

5、双向广搜

//正向逆向交替走,分别标记方向,看某个时刻有没有相遇 
//双向广搜
//要标记正反向的搜索记录 
int n,sx,sy,ex,ey;
int dis[8][2]={{1,2},{-1,2},{1,-2},{-1,-2},{2,1},{-2,-1},{-2,1},{2,-1}};
int vis[maxn][maxn]; //正向是1,逆向是2
int step[maxn][maxn]; 
int xx[1000000],yy[1000000]; //数组模拟队列 
int fun(){
	memset(step,0,sizeof(step));
	memset(vis,0,sizeof(vis));
	int head=0,tail=0;
	xx[tail]=sx;yy[tail++]=sy;vis[sx][sy]=1;
	xx[tail]=ex;yy[tail++]=ey;vis[ex][ey]=2;
	while(head!=tail){
		int x=xx[head];
		int y=yy[head++];
		int t=step[x][y];
		for(int i=0;i<8;i++){
			int l=x+dis[i][0],r=y+dis[i][1];
			if(l<0||l>=n||r<0||r>=n) continue;
			if(vis[l][r]!=vis[x][y]&&vis[l][r]&&vis[x][y]) 
			return step[l][r]+step[x][y]+1; //有了交会 
			if(!vis[l][r]){
				xx[tail]=l;
				yy[tail++]=r;
				step[l][r]=t+1;
				vis[l][r]=vis[x][y];
			}
		}
	}
	return 0;	
}

int main(){

	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d %d %d %d %d",&n,&sx,&sy,&ex,&ey);
		printf("%d\n",fun());
	}
	
return 0;
} 

6、迭代加深搜索:其实就是DFS和BFS的结合,在深度上用BFS进行扩散,但是写法实际上是DFS,

可以避免DFS和BFS两者的缺点

例题:埃及分数

逐步地增大搜索的深度

//利用DFS,BFS,深度上用BFS进行扩展,但总体上用DFS进行搜索 
//埃及分数 
LL ans[maxn],s[maxn],mo,ch;
int dep;
//迭代加深搜索
//https://www.cnblogs.com/hadilo/p/5746894.html
//https://www.cnblogs.com/hchlqlz-oj-mrj/p/5389223.html 
LL gcd(LL a,LL b){
	//返回最大公因数
	return b==0? a:gcd(b,a%b); 
}
void outp(){
	if(ans[dep]>s[dep]){  //如果结果更优 ,ans[dep]>s[dep]说明最后一位大一些 
		for(int i=1;i<=dep;i++){
			ans[i]=s[i];
		}
	}
}
void dfs(LL x,LL y,int d){
	LL a,b,i,w;
	if(d==dep){ //已经到了最后的深度了 
		//如果符合1/a的格式
		s[d]=y;
		if(x==1&&s[d]>s[d+1]) outp(); //且递减 
		return;
	}
	//注意这个下面的范围        //这个是放大 
	for(i=max(s[d-1]+1,y/x+1);i<(dep-d+1)*y/x;i++){
		b=y*i/gcd(y,i);
		a=b/y*x-b/i; //统分就知道了
		w=gcd(a,b);
		a/=w;b/=w;
		s[d]=i;
		dfs(a,b,d+1); 
	}
	
}

int main(){
	scanf("%lld%lld",&ch,&mo);	
	int i=gcd(ch,mo);
	ch/=i;
	mo/=i;
	for(dep=2;;dep++){
		ans[1]=0;
		s[0]=0;        //赋个初值
		ans[dep]=INF;   //赋个初值
		dfs(ch,mo,1);
		if(ans[1]!=0) break; 
	}
	for(int j=1;j<=dep;j++) printf("%lld  ",ans[j]);
	printf("\n");
return 0;
}

7、IDA*对迭代加深搜索的优化,LIKE A*在IDDFS上面的应用

在IDDFS上面增加一个估价函数,然后进行剪枝操作(利用估价函数进行剪枝)

POJ 3134 power calculus

qes:从数字1开始,进行多少次加减操作能够得到数字n

这道题是IDA*和IDDFS的应用,

IDDFS:指定递归深度,每一次做DFS不会超过这个深度

估价函数:如果当前的值用最快的方式:连续乘2都不能到达n,那么就可以剪枝了

int val[maxn]; //保持每个结果 
int pos,n;
bool ida(int now,int dep){  //当前的深度、规定的深度 
	if(now>dep) return false;
	if(val[pos]<<(dep-now)<n) return false; //ida
	if(val[pos]==n) return 1;
	pos++; //往下移
	for(int i=0;i<pos;i++){
		val[pos]=val[pos-1]+val[i]; 
		if(ida(now+1,dep)) return 1;  //DFS
		val[pos]=abs(val[pos-1]-val[i]);  //DFS 
		if(ida(now+1,dep)) return 1;
	} 
	pos--;
	return false;  //别忘了回溯 
}
int main(){
	int t;
	while(cin>>n&&n){
		int dep;
		for(dep=0;;dep++){
			val[pos=0]=1;
			if(ida(0,dep)) break;
		}
		cout<<dep<<endl; 
	}

return 0;
}

  

 

 posted on 2020-02-24 16:54  shirlybabyyy  阅读(6)  评论(0编辑  收藏  举报