zoukankan      html  css  js  c++  java
  • 搜索---从初始状态到目标状态(学习笔记)

    对于这种"求从初始状态到目标状态的步数"的搜索题,BFS是较好的选择.

    移动玩具

    (4*4)的正方形内每个格子上是数字0或1,要由初始状态移动到目标状态,每次移动只能上下左右四个方向移动,求最少的移动次数

    对比两幅图(初始状态和目标状态),对于数字一样的格子(同为0或者同为1),在两幅图上都赋值为0;然后对于初始状态图中剩下未匹配的点(即该点在两幅图中的值不同),开一个结构体记下横纵坐标.

    四维数组(f[i][j][k][l])记录的是([i,j])([k,l])两点之间的曼哈顿距离(即横纵坐标之差的绝对值的和),表示([i,j])要移动到([k,l])需要多少次移动.

    int sum,ans=1e9;
    int a[5][5],b[5][5],f[5][5][5][5],visit[5][5];
    struct A{
        int x,y;
    }point[30];
    //cnt:搜索过程中匹配好的点的数量
    //sum:先前未匹配的点的数量
    //ans,val:移动步数
    void dfs(int cnt,int val){
        if(cnt>sum){ans=min(ans,val);return;}
    //全匹配好了,就比较大小求出最小值
        for(int i=1;i<=4;i++)
    	for(int j=1;j<=4;j++){
    	    if(f[point[cnt].x][point[cnt].y][i][j]&&!visit[i][j]){
    			visit[i][j]=1;
    			dfs(cnt+1,f[point[cnt].x][point[cnt].y][i][j]+val);
    			visit[i][j]=0;
    	    }
    //如果当前要匹配的点[x,y]与搜索到的点[i,j]可以发生匹配,且[i,j]没和其它点匹配过
    	}
    }
    int main(){
        for(int i=1;i<=4;i++)
    	for(int j=1;j<=4;j++){
    	    char ch;cin>>ch;
    	    a[i][j]=ch-'0';
    	}
        for(int i=1;i<=4;i++)
    	for(int j=1;j<=4;j++){
    	    char ch;cin>>ch;
    	    b[i][j]=ch-'0';
    	    if(a[i][j]==b[i][j])
            	a[i][j]=b[i][j]=0;
    	}
    //经过上述步骤:把两个图中相同的1变为0后
    //此时b图中的1对应在a图中一定是0(即是需要匹配,需要被交换位置的0)
        for(int i=1;i<=4;i++)
    	for(int j=1;j<=4;j++){
    	    if(a[i][j]==1){
    			point[++sum].x=i;point[sum].y=j;
    			for(int k=1;k<=4;k++)
    			for(int l=1;l<=4;l++)
    				if(b[k][l]==1)
                		f[i][j][k][l]=abs(l-j)+abs(k-i);
    	    }
    	}
    //所以这里实际上是在a图中把所有1与所有需要匹配的0
    //两两之间的距离算出来存在f数组中
    //为什么要算距离?因为这些1和0两两需要调换位置
    //而我们无法预测某个1一定会和哪个0交换位置
        dfs(1,0);
        printf("%d
    ",ans);
        return 0;
    }
    
    

    本题还可以把(4*4)的格子(状态)通过哈希状态压缩,不同的局面最多只有(2^{16})=65536个,然后跑BFS.

    八数码难题

    (3×3)的棋盘上,摆有八个棋子,每个棋子上标有1至8中的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。

    要求解的问题是:给出一种初始状态和目标状态(目标状态为123804765),找到一种最少步骤的移动方法,实现从初始状态到目标状态的转变。

    发现本题只有(3*3)的格子,也就是只有9位数,那就可以直接用int存下状态.

    为了优化,既然我们知道了初始状态和目标状态,不妨跑双向广搜.

    int st,ed=123804765;
    int a[5][5];
    int dx[4]={1,-1,0,0},
        dy[4]={0,0,1,-1};
    map<int,int> ans,visit;
    queue<int> q;
    void bfs(){
        q.push(st);q.push(ed);
    //把初始状态和目标状态都放入队列中
    //下面赋的初值好好思考一下
        ans[st]=0,ans[ed]=1;
    //就算是双向广搜,也不可能同时走两步(拓展两个状态)
    //一定是先拓展一个状态,再对另一状态进行拓展
    //故设初始状态的ans值为0,则目标状态的ans值就为1    
        visit[st]=1,visit[ed]=2;
    //不同的取值是为了区分是哪个状态拓展的
        while(!q.empty()){
    		int u=q.front(),now=u,zeroi,zeroj;
        	q.pop();
    		for(int i=3;i>=1;i--)
    	    for(int j=3;j>=1;j--){
    			a[i][j]=now%10;
    			now/=10;
    			if(a[i][j]==0){
                	zeroi=i;
                    zeroj=j;
                }
    	    }
    //把int存的状态转为3*3的格子中,同时记录0所在的位置
    		for(int i=0;i<=3;i++){
    			int xx=dx[i]+zeroi;
            	int yy=dy[i]+zeroj;
    			if(xx<1||xx>3||yy<1||yy>3)
                	continue;
    			swap(a[xx][yy],a[zeroi][zeroj]);
    //找到0位置周围一个合法的位置进行位置交换
    			now=0;
    			for(int j=1;j<=3;j++)
    		    for(int k=1;k<=3;k++){
    				now=now*10+a[j][k];
    		    }
    //位置交换后,又把3*3的状态转到int中存下
    			if(visit[now]==visit[u]){
    		    	swap(a[xx][yy],a[zeroi][zeroj]);
    		    	continue;
    			}
    //如果now这个状态之前已经访问过,
    //且与当前出队状态u是同一个状态拓展出的,则不合法
    			if(visit[now]+visit[u]==3){
    		    	printf("%d
    ",ans[now]+ans[u]);
    		    	exit(0);
    			}
    //如果now这个状态之前已经访问过,
    //且1+2=3:
    //即一个是初始状态拓展而来,一个是目标状态拓展而来
    //则说明双向搜索途中相遇了,答案也就出来了
    //通过这两个if语句的判断,理解visit赋值不同的作用
    			ans[now]=ans[u]+1;
    			visit[now]=visit[u];
    			q.push(now);
    //既不是非法状态,又不是答案状态,则记录信息,继续入队
    			swap(a[xx][yy],a[zeroi][zeroj]);
    //最后记得把格子复原(因为当前状态已存入int中入队)
        }
    }
    int main(){
        st=read();
        if(st==ed){
    		puts("0");
    		return 0;
        }//特判一下
        bfs();
        return 0;
    }
    
    
  • 相关阅读:
    postgresql数据迁移
    编译安装或者mysql启动时遇到的错误小记
    安装数据库时提示重启删除 以下注册信息则不用重启
    ​sql2008安装提示报错,.必须使用"角色管理工具"安装或配置Microsoft.net.framework 3.5 sp1
    windows10 安装jdk13.0.1 与 apache-jmeter-5.1.1
    Axure RP 9 获取验证码发送倒计时
    PHP 重定向跳转页面使用post传参
    用 PHPExcel 导入excel表格并展示到前台
    当失去焦点时 验证时分秒 并提示
    iptables 防止syn ddos ping攻击
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10310748.html
Copyright © 2011-2022 走看看