算法(四)搜索

一、深度优先搜索(DFS)

1、概念

DFS(Depth First Search)是从初始结点开始扩展,扩展顺序总是先扩展最新产生的结点。这就使得搜索沿着状态空间某条单一的路径进行下去,直到最后的结点不能产生新结点或者找到目标结点为止。当搜索到不能产生新的结点的时候,就沿着结点产生顺序的反方向寻找可以产生新结点的结点,并扩展它,形成另一条搜索路径。

2、剪枝策略

剪枝就是通过一些判断,剪掉搜索树上不必要的子树。在采用DFS算法搜索时,有时候我们会发现某个结点对应的子树的状态都不是我们要的结果,这时候我们没必要对这个分支进行搜索,砍掉这个子树,就是剪枝。

在DFS搜索算法中,剪枝策略就是寻找过滤条件,提前减少不必要的搜索路径。应用剪枝策略的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。

有时没有剪枝或剪枝不全:答案错误,压爆栈(特别是模拟类、地图搜索问题)等

剪枝策略按照其判断思路可大致分成两类:可行性剪枝及最优性剪枝。

(1)可行性剪枝

可行性剪枝就是把能够想到的不可能出现的情况给它剪掉 。该方法判断继续搜索能否得出答案,如果不能直接回溯。

(2)最优性剪枝

最优性剪枝,又称为上下界剪枝,是一种重要的剪枝策略。它记录当前得到的最优值,如果当前结点已经无法产生比当前最优解更优的解时,可以提前回溯。
对于求最优解**(最小值)**的一类问题,通常可以用最优性剪枝。比如在求迷宫最短路的时候,如果发现当前的步数已经超过了当前最优解,那从当前状态开始的搜索都是多余的,因为这样搜索下去永远都搜不到更优的解。通过这样的剪枝,可以省去大量冗余的计算。

(一)非回溯

1、概念

DFS中一般用于操作个数不确定且终止值不连续的情况。

2、解题思路

剪枝:剪去已访问及不符合题意的点,直接return

(题意给的特殊值情况)

终止条件,否则拓展

	public static void dfs(int x,int y,int mou,int times) {
		//1、剪枝:剪去已访问及不符合题意的点,直接return
		if(mou==0 || times>m*n || times>minans || x<=0 
           || x>n || y<=0 || y>m || a[x][y]==0)  return; 
       
        //2、处理,v数组或根据题意给的特殊值情况
		if(a[x][y]==4)  mou=6;
        //v[] = 1;
        
        //3、终止条件
		if(a[x][y]==3) { 
			minans=Math.min(times, minans);
		}//否则拓展
        else {
    		dfs(x + 1,y,mou - 1,times + 1);
    		dfs(x - 1,y,mou - 1,times + 1);
    		dfs(x,y + 1,mou - 1,times + 1);
    		dfs(x,y - 1,mou - 1,times + 1);
		}
        
        //4、回溯
        //v[] = 0;
	}

3、地图搜索问题??

解题思路

a.剪枝:剪去已访问及不符合题意的点,直接return(先做不符合的希望后续统一更改)

b.接下来符合条件,每个点可沿题目给定的几个方向拓展

技巧

a.适用v数组记录已访问的点,防止重复计算要统计的点及爆栈。一半要但非必需,如(1)(2)用

(3)(4)不用:(3)使用了数组值进行区分,(4)有条件剪枝

b.

3.1 简单遍历

a.黑色方块问题

https://www.cnblogs.com/cs-whut/p/11147213.html

void dfs(int x, int y)
{
    if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0)
    {
        visit[x][y] = 1;
        sum++;
        dfs(x+1,y);        // 递归访问四个方向的砖块
        dfs(x-1,y);
        dfs(x,y+1);
        dfs(x,y-1);
    }
}
3.2 连通块

a.求细胞数量

为什么两次判断?主函数:起点不能重且符合条件,dfs:拓展点不能重且符合条件

import java.util.*;
 
public class Main
{
	static int m;
	static int n;
	static int[][] q;
	static int[][] v;
    public static void main(String args[]) {
    	Scanner in = new Scanner(System.in);
    	n = in.nextInt();
    	m = in.nextInt();
    	int sum = 0;
    	q = new int[n][m];
    	v = new int[n][m];
    	in.nextLine();
    	for(int i = 0;i < n;i++) {
    		String s = in.nextLine();
    		char[] c = s.toCharArray();
    		for(int j = 0;j < m;j++) {
    			q[i][j] = c[j] - '0';
    			v[i][j] = 0;
    		}
    	}
    	for(int i = 0;i < n;i++) {
    		for(int j = 0;j < m;j++) {
    			if(q[i][j] != 0 && v[i][j] == 0) {
    				sum++;
    				dfs(i,j);
    			}
    		}
    	}
    	System.out.println(sum);
    }
    
    public static void dfs(int x,int y) {
    	if(x >= 0 && x <= n - 1 && y >= 0 && y <= m - 1 && v[x][y] == 0 && q[x][y] != 0) 		{
    		v[x][y] = 1;
    		dfs(x + 1,y);
    		dfs(x - 1,y);
    		dfs(x,y + 1);
    		dfs(x,y - 1);
    	}
    }
}

b.Lake Counting S

//八个方向
public static void dfs(int x,int y) {
    	if(x >= 0 && x <= m - 1 && y >= 0 && y <= n - 1 && q[x][y] == 'W' && v[x][y] == 0) {
    		v[x][y] = 1;
    		dfs(x + 1,y);
    		dfs(x - 1,y);
    		dfs(x,y + 1);
    		dfs(x,y - 1);
    		dfs(x - 1,y - 1);
    		dfs(x - 1,y + 1);
    		dfs(x + 1,y - 1);
    		dfs(x + 1,y + 1);
    	}
    }
3.3 染色问题

a.拯救oibh总部

为什么要外搜一圈呢??

import java.util.*;
 
public class Main
{

	static int n;
	static int m;
	static int[][] q = new int[500][500];
    public static void main(String args[]) {
    	Scanner in = new Scanner(System.in);
    	n = in.nextInt();
    	m = in.nextInt();
    	int sum = 0;
    	for(int y = 1;y <= n;y++) {
    		String s = in.next();
        	char[] c = s.toCharArray();
    		for(int x = 1;x <= m;x++) {
    			if(c[x - 1] == '0') {
    				q[x][y] = 0;
    			}else {
    				q[x][y] = 1;
    			}
    		}
		}
        //染色,从外圈开始?
    	dfs(0,0);
        //统计染色后仍为0的数量,即保护到的数量
    	for(int x = 1;x <= m;x++) {
    		for(int y = 1;y <= n;y++) {
    			if(q[x][y] == 0) {
    				sum++;
    			}
    		}
    	}
    	System.out.println(sum);
    }
    
    public static void dfs(int x,int y) {
        //外搜一圈??
    	if(x >= 0 && x <= m+1  && y >= 0 && y <= n+1 && q[x][y] == 0) {
            //将水淹没的地方标为2
    		q[x][y] = 2;
    		dfs(x + 1,y);
    		dfs(x - 1,y);
    		dfs(x,y + 1);
    		dfs(x,y - 1);
    	}
    }
    	
}

b.填涂颜色

import java.util.*;
 
public class Main
{

	static int n;
	static int[][] q = new int[901][901];
    public static void main(String args[]) {
    	Scanner in = new Scanner(System.in);
    	n = in.nextInt();
    	for(int x = 1;x <= n;x++) {
    		for(int y = 1;y <= n;y++) {
    			q[x][y] = in.nextInt();
    		}
    	}
    	dfs(0,0);
    	for(int x = 1;x <= n;x++) {
    		for(int y = 1;y <= n;y++) {
    			if(q[x][y] == 0) {
    				q[x][y] = 2;
    			}else if(q[x][y] == 3) {
    				q[x][y] = 0;
    			}
    			System.out.print(q[x][y]);
                //注意空格
    			if(y < n) {
    				System.out.print(" ");
    			}
    		}
    		System.out.println("");
    	}
    }
    
    public static void dfs(int x,int y) {
    	if(x >= 0 && x <= n+1  && y >= 0 && y <= n+1 && q[x][y] == 0) {
    		q[x][y] = 3;
    		dfs(x + 1,y);
    		dfs(x - 1,y);
    		dfs(x,y + 1);
    		dfs(x,y - 1);
    	}
    }
    	
}
3.4 模拟问题

回家

缺少剪枝:会爆栈!

​ 可行性剪枝:times>m*n ,不可能大于mn之积

​ 最优性剪枝:times>minans,要求的是最小

import java.util.Scanner;
public class Main {
	
	static int n=0,m=0,tx=0,ty=0,lx=0,ly=0,minans=1<<30;  //minans 1乘以2的30次方
	static int[][] a=new int[11][11];
	
	public static void main(String[] args) {	
		Scanner in=new Scanner(System.in);
        n=in.nextInt();
        m=in.nextInt();
        
        for(int i=1;i<=n;i++) {
        	for(int j=1;j<=m;j++) {
        		a[i][j]=in.nextInt();
        		if(a[i][j]==2) {        //记录开始点
        			tx=i;
        			ty=j;
        		}
        	}
        }
        dfs(tx,ty,6,0);
        
        //如果此时 minans 与初值相等,则没有找到可行路径
        if(minans!=1<<30) {
        	System.out.println(minans);
        }else {
        	System.out.println("-1");
        }
	}
	
	public static void dfs(int x,int y,int mou,int times) {
		//如果血量为0或者超出步数上界或者此时的步数已经超过了答案
		if(mou==0 || times>m*n || times>minans || x<=0 || x>n || y<=0 || y>m || a[x][y]==0)  return; 
		if(a[x][y]==4)  mou=6;
		if(a[x][y]==3) { 
			//如果到达终点
			minans=Math.min(times, minans);
		}else {
    		dfs(x + 1,y,mou - 1,times + 1);
    		dfs(x - 1,y,mou - 1,times + 1);
    		dfs(x,y + 1,mou - 1,times + 1);
    		dfs(x,y - 1,mou - 1,times + 1);
		}
	}
	
}

4、其他模拟问题

对于此类问题,理论上来说可以用回溯做,但若条件比较复杂,则最好还是用非回溯

一般还会搭配其他思想,如(2)中还使用了贪心思想,故以后遇到再做练习

(1)奇怪的电梯

https://www.luogu.com.cn/problem/P1135

import java.util.Scanner;
public class Main {
	
	static int n;
	static int a,b;
	static int[] rule = new int[300]; 
	static int[] v = new int[300];
	static int min = Integer.MAX_VALUE;
	public static void main(String[] args) {	
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		a = in.nextInt();
		b = in.nextInt();
		for(int i = 1;i <= n;i++) {
			rule[i] = in.nextInt();
			v[i] = 0;
		}
		dfs(a,0);
		if(min == Integer.MAX_VALUE) {
			System.out.println(-1);
		}else {
			System.out.println(min);
		}
		
	}
	
	public static void dfs(int t,int num) {
		if(t < 1 || t > n || num > min || v[t] == 1) return ;
		v[t] = 1;
		if(t == b) {
			min = num;
		}else {
			dfs(t + rule[t],num + 1);
			dfs(t - rule[t],num + 1);
		}
		v[t] = 0;
	}
	
}
(2)Sum It Up?

复杂在于:要去重

https://www.itdaan.com/blog/2018/09/12/ee3c020f4a35870f29c39cba21319555.html

思路:

(1)可行性剪枝: 因为题目中所有待选数都是正数,因此一旦发现当前的和值sum都已经大于t了,那么之后不管怎么选,和值都不可能回到t,所有当sum > t时,可以直接剪掉。

(2)从大到小:因给的数从大到小,故只需从前往后遍历即可

(3)注意防止结果式子重复?

i==k||a[i]!=a[i-1]

#include <iostream>
#include <stdio.h>
#include <algorithm>

using namespace std;
int n,t,a[13],f=1,b[13];

void dfs(int len,int k,int sum)
{
    if(sum==0){    //遇到满足题意的,输出。
        f=0;
        printf("%d",b[0]);
        for(int i=1;i<len;i++)
            printf("+%d",b[i]);
        printf("\n");
    }else{
    	for(int i=k;i<n;i++){
            if((i==k||a[i]!=a[i-1])&&sum-a[i]>=0){  //防止重复
                b[len]=a[i];
                dfs(len+1,i+1,sum-a[i]);
            }
   		}
    }
}


int main()
{
    while(1){
        cin>>t>>n;
        f=1;
        if(t==0&&n==0) break;
        for(int i=0;i<n;i++) cin>>a[i];
        printf("Sums of %d:\n",t);
        dfs(0,0,t);
        if(f) printf("NONE\n");
    }
    return 0;
}

(3)The Robbery (POJ 3900)???

复杂在于:物品种类太多,使用贪心思想

思路:

a.dfs

可行性剪枝(剪枝三:题目要求一种钻石要拿则必须全拿)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lu1v5wI0-1595651391751)(C:\Users\ddjj6\AppData\Roaming\Typora\typora-user-images\1583335893893.png)]

b.贪心:选择性价比最高的

https://blog.csdn.net/V5ZSQ/article/details/48894677

(二)回溯

适用于操作个数确定且连续的情况,具体见之前笔记。

二、广度优先搜索(BFS)

1、概念

广度优先搜索BFS(Breadth First Search)也称为宽度优先搜索,它是一种先生成的结点先扩展的策略。

为了便于进行搜索,要设置一个表存储所有的结点。由于在广度优先搜索算法中,要满足先生成的结点先扩展的原则,所以存储结点的表一般采用队列这种数据结构

如果目标结点存在于解答树的有限层上,广度优先搜索算法一定能保证找到一条通向它的最佳路径,因此广度优先搜索算法特别适用于只需求出最优解的问题。当问题需要给出解的路径,则要保存每个结点的来源,也就是它是从哪一个节点扩展来的。

2、解题思路

//1、起始入队
visit = 1;
//2、循环
while(队列不为空){
    //3、出队
    //(4、处理出队元素,判断是否结束)
    //5、出队拓展{
    	//6、符合条件,入队
    	visit = 1;
    }
}

3、技巧

(1)用有visit数组标识结点是否已访问;当求步数时,用visit数组标注访问到某点所需步数,为-1表示未访问

如果没有v数组去重:时间、空间爆表,所以bfs一定要有v数组!!

(2)队列操作

   //C
   int front = 0,rear = 0;      // front为队头指针,rear为队尾指针
   q[rear++]=cur;          		// 初始结点入队
   cur=q[front++];         		// 队头元素出队

  //Java
  Queue<T> q = new LinkedList<T>();
  q.offer(in); 		//入队
  q.poll(); 		//出队并删除
  while(!q.isEmpty)

(3)队列所放元素如何定义

(4)拓展方式:见题意;判断条件:访问与否、题目条件

(5)创建两个节点对象:放置赋值重叠!

in out

4、优化:双向广度搜索

所谓双向广度搜索指的是搜索沿两个方向同时进行:

(1)正向搜索:从初始结点向目标结点方向搜索;

(2)逆向搜索:从目标结点向初始结点方向搜索;当两个方向的搜索生成同一子结点时终止此搜索过程。

广度双向搜索通常有两种方法:

(1)两个方向交替扩展;

(2)选择结点个数较少的那个方向先扩展。

方法(2)克服了两方向结点的生成速度不平衡的状态,可明显提高效率。

https://www.cnblogs.com/cs-whut/p/11157732.html

4.1 双向

(1)定义一个队列

用同一个队列来保存正向和逆向扩展的结点。**开始时,将起点坐标和终点坐标同时入队列。**这样,第1个出队的坐标是起点,正向搜索扩展队列;第2个出队的坐标是终点,逆向搜索扩展队列。…,两个方向的扩展依次交替进行。

(2)使用vis数组

初始时,vis数组的全部元素值为0,由正向扩展来的结点的vis对应元素值置为1,由逆向扩展来的结点的vis对应元素值置为2。

(3)数组判断

若vis[next.x][next.y]==0,则next结点未访问过,将next结点入队并进行相应设置;

若vis[next.x][next.y]!=0,则next结点已访问过。由于vis[cur.x][cur.y]记录的是当前扩展方向(1代表正向,2代表逆向);

若vis[next.x][next.y] != vis[cur.x][cur.y],则表示next结点以前按相反的方向访问过,正向和反向遇到了同一个结点,搜索成功,返回step[cur.x][cur.y]+step[next.x][next.y]+1;

4.2 结点个数少的方向先扩展

(1) 定义两个队列

两个队列q1[]和q2[]分别用于两个方向的扩展,两个队列的队头指针和队尾指针分别为front1、front2和rear1、rear2。将起始节点放入队列q1,将目的节点放入队列q2;

(2)队列q1里的未处理节点比q2中的少(即rear1-front1 < rear2-front2),则扩展队列q1;否则扩展队列q2

int BFS(int start_x,int start_y,int end_x,int end_y,int n)
// 在n*n的棋盘中搜索从起点(start_x,strat_y)到终点(end_x,end_y)所需的最少步数
{
     int front1,rear1,front2,rear2,i,flag;
     point cur,next,q1[45001],q2[45001];
     front1=rear1=0;
     front2=rear2=0;
     cur.x = start_x;
     cur.y = start_y;
     vis[start_x][start_y] = 1;  // 设置正向探索标记为1
     q1[rear1++] = cur;          // 起始坐标入正向队列
     next.x = end_x;
     next.y = end_y;
     vis[end_x][end_y] = 2;      // 设置逆向探索标记为2
     q2[rear2++] = next;         // 终点坐标入逆向队列
    
     while (front1 < rear1 && front2<rear2)
     {
            if (rear1-front1 < rear2-front2)
            {
            	cur = q1[front1++]; flag=1;    // 扩展正向队列
            }
            else
            {
            	cur = q2[front2++]; flag=2;    // 扩展逆向队列
            }
           for (i=0; i<8; ++i)
           {
                 next.x = cur.x + dx[i];
                 next.y = cur.y + dy[i];
                 if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)
                     continue;
                 //可拓展
                 if (!vis[next.x][next.y])
                 {
                     vis[next.x][next.y] = flag;    
                     step[next.x][next.y] = step[cur.x][cur.y] + 1;
                     // 扩展正向队列
                     if (flag==1)
                        q1[rear1++] = next;
                     // 扩展逆向队列
                     else
                        q2[rear2++] = next;
                 }
                 else if (vis[cur.x][cur.y] != vis[next.x][next.y])
                 {
                        return step[cur.x][cur.y]+step[next.x][next.y]+1;
                 }

           }
    }

    return -1;  // 若搜索不成功,表示不可达

}

5、遍历全部

处理出队元素这里没有结束判断

(1)黑色方块问题

https://www.cnblogs.com/cs-whut/p/11147348.html

#include <iostream>
using namespace std;
#define N 21

struct Node
{
       int x;
       int y;
};

int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
char map[N][N];
int visit[N][N];
int bfs(int startx, int starty,int w,int h)
{

       Node q[N*N],cur,next;   // q为队列
       int  front,rear;        // front为队头指针,rear为队尾指针
       int i,x,y,sum;    
       front=rear=0;           // 队列q初始化
       sum=0;
       cur.x=startx;   cur.y=starty; 
       //1、起始入队
       visit[startx][starty]=1;
       q[rear++]=cur;          // 初始结点入队
       //2、循环
       while(rear!=front)      // 队列不为空
       {
              //3、出队
              cur=q[front++];     // 队头元素出队
              //4、处理出队元素
              sum++;              // 方砖计数
              //5、拓展
              for (i=0;i<4;i++)
              {
                 //6、符合条件入队
                 x=cur.x+dx[i];  y=cur.y+dy[i];
                 if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0)
                 {
                        visit[x][y] = 1;
                        next.x=x;  next.y=y;  // 由cur扩展出新结点next
                        q[rear++]=next;       // next结点入队
                  } 
              }
       }
       return sum;
}

int main()
{
   int i,j,pos_x,pos_y,w,h,sum;
   while(1)
   {
       cin>>w>>h;
       if (w==0 && h==0) break;
       for(i=0;i<h;i++)
       {
          for(j=0;j<w;j++)
          {
              cin>>map[i][j];
              if (map[i][j]=='@')
              {
                pos_x = i;
                pos_y = j;
              }
              visit[i][j] = 0;
         }
      }
      sum=bfs(pos_x, pos_y,w,h);
      cout<<sum<<endl;
   }
   return 0;

}

6、求最优、最短解

处理出队元素这里有结束判断

(1)Knight Moves (POJ 2243)

求最优、最短解

(2)整数变换

用有visit数组标识结点是否已访问;当求步数时,用visit数组标注访问到某点所需步数,为-1表示未访问

https://www.cnblogs.com/cs-whut/p/11150285.html

(3)质数变换?

**拓展:**得到只有一位不同的数

**打表:**质数,10的倍数

https://www.cnblogs.com/cs-whut/p/11150285.html

#include <iostream>  
using namespace std;
char prime[10000];

void GetPrime()              // 用筛法构建质数判定表 
{
      int i,j;
      for (i=2;i<10000;i++)
              prime[i]='1';
       for(i=2;i<10000;i++)    
       {
             if (prime[i]=='1')
                     for (j=2*i;j<10000;j+=i)
                            prime[j]='0';
       }
       prime[1]='0'; 
}

void BFS(int k,int m)
{
    int visit[10000],que[10000],a[4]={1,10,100,1000};	//打表!!
    int front,rear,i,j,s,x,y,num;
    for (i=1000;i<10000;i++)							//一个个初始化为-1!!!
              visit[i]=-1;
    front=rear=0;
    que[rear++]=k;              // k入队  
    visit[k]=0;
    while(front!=rear)
    {
           s=que[front++];         // 队头元素出队
           if (s==m)
           {
                     cout<<visit[s]<<endl;
                     return;
           }
           for (i=0;i<4;i++)
           {    
              //j是变换的位
              for (j=0;j<10;j++)
              {
                  //求一个不同的数???
                  //取前
                  x=s/(a[i]*10);
                  //取后
                  y=s%a[i];
                  num=x*a[i]*10+j*a[i]+y;
                  if (num>1000 && visit[num]==-1 && prime[num]=='1')  
                  {   
                    	que[rear++]=num;     // 变换后的质数入队
                    	visit[num]=visit[s]+1;
                   }
               }
            }
     }
    cout<<"Impossible"<<endl;
}

int main()
{  
    int n,k,m;
    GetPrime();
    cin>>n; 
    while (n--) 
    { 
        cin>>k>>m; 
        BFS(k,m); 
    } 
    return 0;
}

(4)医院设置?

由于二叉树可往上进行,故使用临界数组存储节点可达关系

import java.util.*;
public class Main {
	
	public static class Node{
		int index,step;
		public Node(int index,int step) {
			this.index = index;
			this.step = step;
		}
	}
	
	static int[][] g = new int[101][101];
	static int[] num = new int[101];
	static int n;
	public static void main(String[] args) {	
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		for(int i = 1;i <= n;i++) {
			num[i] = in.nextInt();
			int l = in.nextInt();
			int r = in.nextInt();
            //不要写错!!!
			if(l != 0) {
				g[i][l] = g[l][i] = 1;
			}
			if(r != 0) {
				g[i][r] = g[r][i] = 1;
			}
		}
		int min = Integer.MAX_VALUE;
		for(int i = 1;i <= n;i++) {
			int sum = bfs(i);
			if(sum < min) {
				min = sum;
			}
		}
		System.out.println(min);
	}
	
	public static int bfs(int start) {
		int sum = 0;
		Node out,in;
		int[] v = new int[101];
		for(int i = 1;i <= 100;i++) {
			v[i] = 0;
		}
		Queue<Node> q = new LinkedList<Node>(); 
		in = new Node(start,0);
		q.offer(in);
		v[start] = 1;
		while(!q.isEmpty()) {
			out = q.poll();
			sum += num[out.index] * out.step;
			for(int i = 1;i <= n;i++) {
				if(g[out.index][i] == 1 && v[i] == 0) {
					in = new Node(i,out.step + 1);
					q.offer(in);
					v[i] = 1;
				}
			}
		}
		return sum;
	}
}

(5)马的遍历

n m 不要写错了

import java.util.*;
public class Main {
	
	public static class Node{
		int i,j,step;
		public Node(int i,int j,int step) {
			this.i = i;
			this.j = j;
			this.step = step;
		}
	}
	
	static int[][] map;
	static int[][] v;
	static int[] x = new int[] {1,1,-1,-1,2,2,-2,-2};
	static int[] y = new int[] {2,-2,2,-2,1,-1,1,-1};
	static int n;
	static int m;
	public static void main(String[] args) {	
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		map = new int[n + 1][m + 1];
		v = new int[n + 1][m + 1];
		for(int i = 1;i <= n;i++) {
			for(int j = 1;j <= m;j++) {
				map[i][j] = -1;
			}
		}
		int si = in.nextInt();
		int sj = in.nextInt();
		bfs(si,sj);
		for(int i = 1;i <= n;i++) {
			for(int j = 1;j <= m;j++) {
                //左对齐,宽5格
				System.out.printf("%-5d",map[i][j]);
			}
			System.out.println();
		}
	}
	
	public static void bfs(int si,int sj) {
		Node in,out;
		Queue<Node> q = new LinkedList<Node>();
		in = new Node(si,sj,0);
		q.offer(in);
		while(!q.isEmpty()) {
			out = q.poll();
			map[out.i][out.j] = out.step;
			v[out.i][out.j] = 1;
			for(int i = 0;i < 8;i++) {
				int ini = out.i + x[i];
				int inj = out.j + y[i];
				if(ini >= 1 && ini <= n && inj >= 1 && inj <= m && v[ini][inj] == 0) {
					in = new Node(ini,inj,out.step + 1);
					q.offer(in);
					v[ini][inj] = 1;
				}
			}
		}
		
	}
}

(6)棋盘????

https://www.luogu.com.cn/problem/P3956

7、搜索状态判重(bfs+hash)

在状态判重方法中,hash法是一种常用的方法。常用于对判断数组的压缩以节约空间。

hash法重点在于hash函数的构造。

a.排序的压缩

对n个数字的排列,可构造vis[n!]的数组存储遍历情况

​ n! 与n个数字组成的全排列如何映射呢?我们以3个数字1、2、3组成的全排列来说明问题。

​ 设排列中所有数字满足从小到大排列,则称为正序。不是正序的排列中一定存在某个数字k后面有若干个数字比k小,比k小的数字个数n称为k的逆序个数。

​ 例如,123的各位逆序个数序列为:0,0,0。映射整数为:0=02!+01!+0*0!=0

​ 132的各位逆序个数序列为:0,1,0。映射整数为:1=02!+11!+0*0!=1

​ 213的各位逆序个数序列为:1,0,0。映射整数为:2=12!+01!+0*0!=2

​ 231的各位逆序个数序列为:1,1,0。映射整数为:3=12!+11!+0*0!=3

​ 312的各位逆序个数序列为:2,0,0。映射整数为:4=22!+01!+0*0!=4

​ 321的各位逆序个数序列为:2,1,0。映射整数为:5=22!+11!+0*0!=5

​ 对6位数 654312而言,各位逆序个数序列为:5,4,3,2,0,0,应映射为:55!+44!+33!+22!+01!+00!=600+96+18+4=718。

实现方式

int fact[]={1,1,2,6,24,120};   //  对应0!,1!,2!,3!,4!,5!

int hash(char *s)                 // 把1..6的排列映射为数字 0..(6!-1)
{
       int i, j, temp, num;
       num = 0;
       for (i = 0; i <6-1; i++)
       {
            temp = 0;
            for (j = i + 1; j < 6; j++)
            {
                  if (s[j] < s[i])
                       temp++;
           }
           num += fact[6-i-1] * temp;
       }
       return num;
}

(1)最少交换

思路:

(1)用字符数组读取易于操作

(2)使用hash压缩

https://www.cnblogs.com/cs-whut/p/11162392.html

#include <stdio.h>
#include <string.h>

int fact[]={1,1,2,6,24,120};   //  对应0!,1!,2!,3!,4!,5!

int hash(char *s)                 // 把1..6的排列*s 映射为数字 0..(6!-1)
{
       int i, j, temp, num;
       num = 0;
       for (i = 0; i <6-1; i++)
       {
            temp = 0;
            for (j = i + 1; j < 6; j++)
            {
                  if (s[j] < s[i])
                       temp++;
           }
           num += fact[6-i-1] * temp;
       }
       return num;
}

int BFS(char src[],char dest[])
{
       int vis[720],front,rear,s0,s1,ts,i;
       for(int i = 0;i < 720;i++){
        vis[i] = -1;
       }
       char q[720][7],cur[7],next[7],tmp;
       front=rear=0;
       s1=hash(dest);
       strcpy(q[rear++],src);
       s0=hash(src);
       vis[hash(src)]=0;
       while (front<rear)
       {
              strcpy(cur,q[front++]);  // 出队列
              s0=hash(cur);
              if (s0==s1)             // 达到目标状态
                    return  vis[s0];
              for (i=0;i<6-1;i++)
              {
                    strcpy(next,cur);
                    tmp=next[i]; next[i]=next[i+1];next[i+1]=tmp;  // 交换位置i和i+1中的数字
                    ts=hash(next);
                    if (vis[ts]==-1)   // 状态未出现过
                     {
                            vis[ts]=vis[s0]+1;  // 记录步数
                            strcpy(q[rear++],next);
                     }
              }

    }
}
int main()
{
         char src[7],dest[7];
         while(scanf("%s%s",src,dest)!=EOF)
        {
              printf("%d\n",BFS(src,dest));
        }

        return 0;

}


(2)八数码难题???

​ 为保存状态空间,定义三个全局数据:

​ char visited[MAXN]; // visited[i]=1表示状态i被访问过;为0,表示未被访问

​ int parent[MAXN]; // parent[i]=k 表示状态结点i是由结点k扩展来的

​ char move[MAXN]; // move[i]=d 表示状态结点i是由父节点按照方式d扩展来的

https://www.cnblogs.com/cs-whut/p/11162663.html

bfs核心操作

while(!que.empty())
{
       cur = que.front();  
       que.pop();     
       for(i=0; i<4; ++i)
	   {    
          if(!(i==0 && cur.space<3 || i==1 && cur.space%3==0 || i==2 && cur.space%3==2 ||i==3 && cur.space>5))
          {  
              nst = cur;          
              nst.space = cur.space-5+2*(i+1);      
              nst.board[cur.space]=nst.board[nst.space];
              nst.board[nst.space]=9;
              k = hash(nst.board);   
              if(visited[k] != 1)
			  {   
                        move[k] = i;      
                        visited[k] = 1;       
                        parent[k] = hash(cur.board);         
                        if(k == 0)  //目标结点hash值为0    
                           return;           
                        que.push(nst);             
			  }          
	      }  
	   } 
}

输出路径

void print_path()
{ 
   int n, u;  
   char path[1000]; 
   n = 1;   
   path[0] = move[0];
   u = parent[0];  
   while(parent[u] != -1)
   {      
      path[n] = move[u]; 
      ++n;    
      u = parent[u];
   }  
   for(int i=n-1; i>=0; --i)
   {       
       cout<<md[path[i]];
   }
   cout<<endl;
}

三、DFS和BFS的比较

在采用BFS搜索时,BFS是按一层一层来访问的,层层搜索每一层就代表了一步。BFS优先访问的是兄弟节点,只有这一层全部访问完才能访问下一层,也就是说BFS第几层就代表从起点最少需要多少歩走到当前层的结点;而DFS是按递归来实现的,它优先搜索深度,到一条路径走不下去了再回溯,优先访问的是没有访问过的子结点。

由此可体会:BFS用来搜索最短径路的解是比较合适的,比如求最少步数的解,最少交换次数的解。因为BFS搜索过程中遇到的解一定是离根最近的,所以遇到一个解,一定就是最优解,此时搜索算法可以终止。这个时候一般不适宜使用DFS,因为DFS搜索到的解不一定是离根最近的,只有全局搜索完毕,才能从所有解中找出离根的最近的解。

DFS适合搜索全部的解,因为要搜索全部的解,那么BFS搜索过程中,遇到离根最近的解,并没有什么用,也必须遍历完整棵搜索树,DFS搜索也会搜索全部,但是相比DFS不用记录过多信息,所以搜素全部解的问题,DFS显然更加合适。

DFS:尽量使用,全部遍历及部分最小解、最大解

BFS:大多数求最小解时使用

https://www.cnblogs.com/cs-whut/p/11149521.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值