算法1-7 搜索

目录

1 深度优先搜索

1.1 P1219 八皇后 

 1.2 P1135 深搜+剪枝

1.3 P1605 多路深搜+回溯

 2 广度优先搜索

2.1 P1443 马的遍历

3 多方向搜索 

3.1 P1101 单词方阵


1 深度优先搜索

需要考虑深度的情况

  • 固定长度组合:当问题要求生成确定长度的组合(如从n个元素中选择k个)时,必须通过深度参数(或计数器)跟踪已选元素的数量。例如,在生成组合C(n, k)时,若当前路径长度等于k,则终止递归。
  • 剪枝优化:深度参数可用于剪枝,提前终止无法满足长度条件的路径,提高效率。

不需考虑深度的情况

  • 所有子集生成:当问题允许任意长度的组合(如求所有子集)时,无需显式跟踪深度。DFS会通过元素索引隐式控制递归层次,遍历所有可能的选与不选路径,最终生成所有子集。

不考虑深度的结果

  • 若问题本需固定长度但未控制深度,DFS将生成所有可能的子集(包含不同长度),而非目标长度的组合。
  • 若问题允许任意长度,正确实现的DFS(通过索引控制遍历顺序)会列举所有组合情况。

1.1 P1219 八皇后 

思路:

  • 题目要求找到满足条件的解,我们需要去搜索可行解,每行进行一次递归,找到对应值
  • 难点:判断该点的两条对角线上没有其他点,即标记该点对角线的使用状态
  • 使用四个数组分别代表行,列,两条对角线,左上到右下的对角线y-i是个定值,为避免y-i<0,令y-i+n,偏移n个单位,依然对应每条对角线即可;左下到右上的对角线i+j为定值,所以只需要使用两个数组来保存每条对角线是否使用即可
package SouSuo;
import java.util.Scanner;

public class P1219 {
    public static int count=0;
    public static int n;
    // 记录每行存储的列下标
    static int[]a;
    // 记录哪一列被使用
    static int[]b;
   // 左下到右上对角线
    static int[]c;
    // 左上到右下对角线
    static int[]d;

    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        n = scanner.nextInt();
        a=new int[n+1];
        b=new int[n];
        c=new int[2*n];
        d=new int[2*n];
        dfs(1);
        System.out.println(count);
    }

    public static void dfs(int index){
        if (index>n){
            if (count<3){
                for (int i = 1; i <=n ; i++) {
                    System.out.print(a[i]+" ");
                }
                System.out.println();
            }
            count++;
            return;
        }
        for (int i = 0; i < n; i++) {
            // 第i列未使用 该位置的左下到右上的对角线行+列固定值位index+i
            if (b[i]==0 && c[index+i]==0 && d[i-index+n]==0){
                b[i]=1;c[index+i]=1;d[i-index+n]=1;
                // index是行号 i是列
                a[index]=i+1;
                dfs(index+1);
                b[i]=0;c[index+i]=0;d[i-index+n]=0;
            }
        }
    }
}

 1.2 P1135 深搜+剪枝

思路:

  • 每层楼梯都可以上或者下,可以分两路递归,一路上,一路下
  • 方法开始时对当前层判断是否合法,而不在上楼和下楼前判断其合法性
  • 关键点:剪枝
    • 当前层数大于最大层或者小于最低层,索引位置不合法
    • 在不断上下楼的过程中,一层楼会反复到达,从这层楼到达其它楼层的方案又一致,导致大量的重复计算,使用一个数组记录到达每层时的最小步数,如果下次到达该层时>=该步数,直接返回
    • 有了上述两次剪枝后,可能达到某个点时花费了5步,比之前到达这个点花费的步数少,但可能之前3步就走到了最终点,即当前方案应该舍去,直接return,即第三种剪枝

1.3 P1605 多路深搜+回溯

思路:

  • 从起点到终点,搜索有多少条可行路径,每段路径一个点只能走一次
  • 采用深度搜索,多路递归模拟上下左右四种选择情况
  • 使用flags数组标记该点的状态,当从这个点出发,上下左右都走完时,会回到上一个出发点,重新开始一条路径,则将当前点回溯为false

细节:

  • 每次对当前点做越界判断,而不是判断该点是否可以向左走,向右走,向上走,向下走等多种情况
package SouSuo;

import java.util.Scanner;

public class P1605 {
    public static int n;
    public static int m;
    public static int sx;
    public static int sy;
    public static int fx;
    public static int fy;
    public static int count;

    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        int t = scanner.nextInt();
        sx = scanner.nextInt();
        sy = scanner.nextInt();
        fx = scanner.nextInt();
        fy = scanner.nextInt();
        int[][] array=new int[n+1][m+1];
        for (int i = 0; i < t; i++) {
            // 障碍点
            array[scanner.nextInt()][scanner.nextInt()]=-1;
        }
        boolean[][]flags=new boolean[n+1][m+1];
        dfs(array,flags,sx,sy);
        System.out.println(count);
    }

    public static void dfs(int[][] array,boolean[][]flags,int x,int y){
        if (x<=0 || y<=0 || x>n ||y>m){
            return;
        }
        if (array[x][y]==-1){
            // 这个点是障碍点
            return;
        }
        if (x==fx && y==fy){
            count+=1;
            return;
        }
        if (flags[x][y]){
            return;
        }
        flags[x][y]=true;
        dfs(array,flags,x+1,y);
        dfs(array,flags,x-1,y);
        dfs(array,flags,x,y+1);
        dfs(array,flags,x,y-1);
        // 这个点上下左右都走完了 回溯到上一个点 把这个点放掉
        flags[x][y]=false;
    }
}

 2 广度优先搜索

2.1 P1443 马的遍历

思路:

  • 题目给了一个初始点,问马到棋盘上其它点的最小步数,采用广度优先搜索,因为要的是最小步数,马在选择下一个点时有多种情况,而这多种情况是在同一层的,即步数相等,而不应该逐渐增加
  • 下一个点的步数等于当前点的步数+1,不需要采用count计数,不然还需要考虑count回溯问题

细节:

  • 马可以有八种选择,使用两个数组dx,dy分别代表对应一组数据的变化情况,避免if枚举
package SouSuo;

import java.util.LinkedList;
import java.util.Scanner;

public class P1443 {
    public static int n;
    public static int m;

    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        int x = scanner.nextInt();
        int y = scanner.nextInt();
        int[][]array=new int[n+1][m+1];
        array[x][y]=0;
        LinkedList<Integer>hangQueue=new LinkedList<>();
        LinkedList<Integer>lieQueue=new LinkedList<>();
        // 使用这个数组 每次拿到一种x和y 避免手动枚举ifelse
        int[]dx={-2,-2,2,2,1,-1,1,-1};
        int[]dy={-1,1,-1,1,2,-2,-2,2};
        hangQueue.offer(x);
        lieQueue.offer(y);
        while(!hangQueue.isEmpty()){
            // 记录下一层元素个数
            Integer xx = hangQueue.poll();
            Integer yy = lieQueue.poll();
            for (int i = 0; i < 8; i++) {
                // 8种选择方式
                int  nextX= xx + dx[i];
                int nextY = yy + dy[i];
                if (nextX>=1 && nextX<=n && nextY>=1 && nextY<=m 
                && array[nextX][nextY]==0){
                    // 这个点可以走 步数等于走到这个点的步数+1
                    array[nextX][nextY]=array[xx][yy]+1;
                    hangQueue.offer(nextX);
                    lieQueue.offer(nextY);
                }
            }
        }
        // 这个初始点值为0 可能再后期又被来过 再次赋值为0
        array[x][y]=0;
        for (int i = 1; i < array.length; i++) {
            for (int j = 1; j < array[i].length; j++) {
                if (array[i][j]==0 && (i!=x || j!=y)){
                    System.out.print(-1+" ");
                }else{
                    System.out.print(array[i][j]+" ");
                }
            }
            System.out.println();
        }
    }

}

在赋值步数时,也可以使用层次的方式赋值,每次统计该层添加的个数,下次循环该个数即可拿到每层的元素

package SouSuo;

import java.util.LinkedList;
import java.util.Scanner;

public class P1443 {
    public static int n;
    public static int m;

    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        int x = scanner.nextInt();
        int y = scanner.nextInt();
        int[][]array=new int[n+1][m+1];
        array[x][y]=0;
        LinkedList<Integer>hangQueue=new LinkedList<>();
        LinkedList<Integer>lieQueue=new LinkedList<>();
        int[]dx={-2,-2,2,2,1,-1,1,-1};
        int[]dy={-1,1,-1,1,2,-2,-2,2};
        hangQueue.offer(x);
        lieQueue.offer(y);
        int number=1;
        int count=0;
        while(!hangQueue.isEmpty()){
            // 记录下一层元素个数
            int geshu=0;
            count++;
            for (int j = 0; j < number; j++) {
                Integer xx = hangQueue.poll();
                Integer yy = lieQueue.poll();
                for (int i = 0; i < 8; i++) {
                    // 8种选择方式
                    int  nextX= xx + dx[i];
                    int nextY = yy + dy[i];
                    if (nextX>=1 && nextX<=n && nextY>=1 && nextY<=m && array[nextX][nextY]==0){
                        // 这个点可以走 步数等于走到这个点的步数+1
                        array[nextX][nextY]=count;
                        hangQueue.offer(nextX);
                        lieQueue.offer(nextY);
                        geshu++;
                    }
                }
            }
            number=geshu;
        }
        array[x][y]=0;
        for (int i = 1; i < array.length; i++) {
            for (int j = 1; j < array[i].length; j++) {
                if (array[i][j]==0 && (i!=x || j!=y)){
                    System.out.print(-1+" ");
                }else{
                    System.out.print(array[i][j]+" ");
                }
            }
            System.out.println();
        }
    }

}

3 多方向搜索 

3.1 P1101 单词方阵

思路:

  • 在这个n×n的方阵里搜索一个单词,该单词摆放时有8种方向,即搜索时有8个方向
  • 关键点
    • 定义dx,dy数组列举出每个方向x,y的变化值
    • 搜索触发条件为碰到 'y' 这个字母时
  • 细节:
    • 因为有公用单词的存在,在标记这个单词时不能直接将这个字符转为'*'
    • 每一次变化dx,dy,在搜索时使用int L变量记录变化个数,实现数组索引的移动
package SouSuo;

import java.util.Scanner;

public class P1101 {
    public static int[]dx={-1,1,0,0,1,-1,1,-1};
    public static int[]dy={0,0,-1,1,1,-1,-1,1};
    public static char[]yizhong={'y','i','z','h','o','n','g'};
    public static char[][]array;
    public static boolean[][]isChar;
    public static int n;

    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        n = scanner.nextInt();
        array=new char[n][n];
        scanner.nextLine();
        for (int i = 0; i < n; i++) {
            array[i]=scanner.nextLine().toCharArray();
        }
        isChar=new boolean[n][n];
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                if (array[i][j]=='y'){
                    dfs(i,j);
                }
            }
        }
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                if (isChar[i][j]){
                    System.out.print(array[i][j]);
                }else{
                    System.out.print('*');
                }
            }
            System.out.println();
        }
    }

    /**
     * 搜索8个方向
     * @param i
     * @param j
     */
    public static void dfs(int i, int j) {
        for (int k = 0; k < 8 ; k++) {
            int xx= dx[k];
            int yy= dy[k];
            int l;
            for (l= 1; l < 7; l++) {
                // 从当前位置向后找7个字符 判断是否相等
                int xxx = i + xx*l;
                int yyy = j + yy*l;
                if (xxx<0 || xxx>n-1 || yyy<0 || yyy>n-1){
                    break;
                }
                if (array[xxx][yyy]!=yizhong[l]){
                    break;
                }
            }
            if (l==7){
                for (l= 0; l < 7; l++) {
                    int xxx = i + xx*l;
                    int yyy = j + yy*l;
                    isChar[xxx][yyy]=true;
                }
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值