《算法笔记》第8章 提高篇(2)——搜索专题

127 阅读2分钟

DFS

使用递归可以很好地实现DFS

  1. 不剪枝
import java.util.Scanner;

public class Test1 {

    final static int maxn=30;
    static int n;//物品件数
    static int V;//背包容量
    static int maxValue;//最大价值
    static int[] w = new int[maxn];//w[i]为每件物品的重量
    static int[] c= new int[maxn];//c[i]为每件物品的价值
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n= scanner.nextInt();
        V=scanner.nextInt();
        for(int i=0;i<n;i++){
            w[i]= scanner.nextInt();
        }
        for(int i=0;i<n;i++){
            c[i]= scanner.nextInt();
        }
        dfs(0,0,0);
        System.out.println(maxValue);
    }
    
    public static void dfs(int currentIndex,int preSumW,int preSumC){
        if(currentIndex==n){//物品编号从0~n-1
            if (preSumW<=V&&preSumC>maxValue){
                maxValue=preSumC;
            }
            return;
        }
        //岔道口
        dfs(currentIndex+1,preSumW,preSumC);//当前物品不放入背包
        dfs(currentIndex+1, preSumW+w[currentIndex], preSumC+c[currentIndex]);//当前物品放入背包
    }
}
  1. 剪枝
/**
 * 剪枝
 */

import java.util.Scanner;

public class Test2 {

    final static int maxn=30;
    static int n;//物品件数
    static int V;//背包容量
    static int maxValue;//最大价值
    static int[] w = new int[maxn];//w[i]为每件物品的重量
    static int[] c= new int[maxn];//c[i]为每件物品的价值

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n= scanner.nextInt();
        V=scanner.nextInt();
        for(int i=0;i<n;i++){
            w[i]= scanner.nextInt();
        }
        for(int i=0;i<n;i++){
            c[i]= scanner.nextInt();
        }
        dfs(0,0,0);
        System.out.println(maxValue);
    }

    public static void dfs(int currentIndex,int preSumW,int preSumC){
       if(currentIndex==n){
           return;
       }
       if(preSumW+w[currentIndex]<=V){//选第currentIndex件物品(是由条件的)
           dfs(currentIndex+1,preSumW+w[currentIndex],preSumC+c[currentIndex]);
           if(preSumC+c[currentIndex]>maxValue){
               maxValue=preSumC+c[currentIndex];
           }
       }
       dfs(currentIndex+1, preSumW, preSumC);//不选currentIndex件物品
    }
}

以上一类常见的DFS问题,本质思想就是:给定一个序列,枚举这个序列的所有子序列(可以不连续),然后再从子序列中选择一个“最优子序列”,使它的某个特征是所有子序列中最优的。

dfs的每个分支就对应序列中的各个元素是选择还是不选择

再看一个例子:(并且要记录最优方案)

/**
 * 递归实现DFS
 * 剪枝
 * 并记录最优方案
 */
public class Test4 {

    static int N;//N个整数
    static int[] nums;//记录N个整数
    static int K;//选K个出来
    static int X;//K个数的和
    static int maxValue;//最大平方和

    static ArrayList<Integer> optList=new ArrayList<>();//存放最优方案
    static ArrayList<Integer> tempList=new ArrayList<>();//dd存放临时方案
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N= scanner.nextInt();
        K= scanner.nextInt();
        X= scanner.nextInt();
        nums=new int[N];
        for(int i=0;i<N;i++){
            nums[i]= scanner.nextInt();
        }
        dfs(0,0,0,0);
        System.out.println(maxValue);
        for (Integer integer : optList) {
            System.out.print(integer+" ");
        }
    }

    /**
     *
     * @param currentIndex 当前数字序号
     * @param preTotals 之前的数字个数
     * @param preX 之前的数字的和
     * @param preMax 之前的数字的平方和
     */
    public static void dfs(int currentIndex,int preTotals,int preX,int preMax){
        if(currentIndex==N){//数字序号为0~N-1
            return;
        }
        if(1+preTotals<=K){//选择当前数  (剪枝叶:只有当 当前选择的数字个数不超过K时才可以选择当前数字)
            tempList.add(nums[currentIndex]);
            dfs(currentIndex+1,preTotals+1,preX+nums[currentIndex],preMax+nums[currentIndex]*nums[currentIndex]);
            if(1+preTotals==K&&preX+nums[currentIndex]==X&&preMax+nums[currentIndex]*nums[currentIndex]>maxValue){//只有当满足条件时才能更新maxValue
                maxValue=preMax+nums[currentIndex]*nums[currentIndex];
           //     optList=tempList;//java集合对象之间写赋值号是浅拷贝,引用同一个对象,这里需要深拷贝
                optList.clear();
                for (Integer integer : tempList) {
                    optList.add(integer);
                }
            }
            tempList.remove((Object)nums[currentIndex]);//不加(object)会认为是下标
        }
        dfs(currentIndex+1,preTotals,preX,preMax);
    }
}

每个数可以选择多次:


/**
 * 递归实现DFS
 * 剪枝
 * 并记录最优方案
 */
public class Test5 {

    static int N;//N个整数
    static int[] nums;//记录N个整数
    static int K;//选K个出来
    static int X;//K个数的和
    static ArrayList<Integer> tempList=new ArrayList<>();//存放临时方案
    static ArrayList<Integer> optList=new ArrayList<>();//存放最优方案
    static ArrayList<ArrayList<Integer>> allOptLists=new ArrayList<>();//存放所有最优方案
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N= scanner.nextInt();
        K= scanner.nextInt();
        X= scanner.nextInt();
        nums=new int[N];
        for(int i=0;i<N;i++){
            nums[i]= scanner.nextInt();
        }
        dfs(0,0,0);
        for (ArrayList<Integer> allOptList : allOptLists) {
            System.out.println(allOptList);
        }
    }

    /**
     *
     * @param currentIndex 当前数字序号
     * @param preTotals 之前的数字个数
     * @param preX 之前的数字的和
     */
    public static void dfs(int currentIndex,int preTotals,int preX){
        if(currentIndex==N){
            return;
        }
        if(1+preTotals<=K){//(可以)选择当前数字
            tempList.add(nums[currentIndex]);
            dfs(currentIndex,preTotals+1,preX+nums[currentIndex]);//每个整数可以选择多次,当前选择了nums[currentIndex],下一回合仍然可以选择nums[currentIndex]
            if(1+preTotals==K&&preX+nums[currentIndex]==X){
                optList.clear();
                ArrayList<Integer> list = new ArrayList<>();   //TODO 1.每次进递归重新分配一个内存地址给list
                for (Integer integer : tempList) {
                    optList.add(integer);
                    list.add(integer);
                }
               // allOptLists.add(optList);//TODO 你妈的 这里也是浅拷贝,还是引用的同一个集合对象,导致了最后allOptLists中的各个集合都是一样的,也就是optList
                allOptLists.add(list);  //TODO 2.则allOptLists每次add进去的就不是同一个集合对象
            }
            tempList.remove(tempList.size()-1);//这里参数不能再写(Object)nums[currentIndex],因为这里根据值来删除的话,会一下子删掉所有重复的值
        }
        dfs(currentIndex+1,preTotals,preX);//不选择当前数字
    }
}

BFS

BFS一般由队列实现

  1. 队列

  2. 增量数组 表示上下左右四个方向

    int[] x={0,0,1,-1};

    int[] y={1,-1,0,0};

  3. boolean型数组inq记录每个位置是否在BFS中已入过队,防止走回头路

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

/**
 *BFS
 */
public class Test6 {

    final static int maxn=100;
    static int m;//矩阵行数  m×n的矩阵
    static int n;//列数
    static int[][] grid=new int[maxn][maxn];//矩阵
    static boolean[][] inq=new boolean[maxn][maxn];//inq[i][j]表示i行j列元素是否入队过    //我觉得对这个题来说可以不需要这个,可以直接把1变0
    //增量数组:
    static int[] x={0,0,-1,1};
    static int[] y={1,-1,0,0};

    public static void main(String[] args) {
        //初始化矩阵
        Scanner scanner = new Scanner(System.in);
        m= scanner.nextInt();
        n= scanner.nextInt();
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                grid[i][j]= scanner.nextInt();
            }
        }
        int cnt=0;
        //统计“块”的个数
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1&&inq[i][j]==false){
                    cnt++;
                    bfs(i,j);//去标记相邻的“1”的inq为true,避免重复访问
                }
            }
        }
        System.out.println(cnt);
    }

    public static void bfs(int row,int col){
        Queue<Node> queue=new LinkedList<>();
        Node node = new Node(row, col);
        queue.offer(node);
        inq[row][col]=true;
        while(!queue.isEmpty()){
            Node topNode = queue.poll();
            int topNodeRow=topNode.row;
            int topNodeCol=topNode.col;
            for(int i=0;i<4;i++){
                int tempNodeRow=topNodeRow+x[i];
                int tempNodeCol=topNodeCol+y[i];
                if(judge(tempNodeRow,tempNodeCol)){
                    queue.offer(new Node(tempNodeRow,tempNodeCol));
                    inq[tempNodeRow][tempNodeCol]=true;
                }
            }
        }
    }

    public static boolean judge(int row,int col){
        if(row<0||row>=m||col<0||col>=n){
            return false;
        }
        if(grid[row][col]==0||inq[row][col]==true){
            return false;
        }
        return true;
    }


}

class Node{
    int row;
    int col;

    public Node(int row,int col){
        this.row=row;
        this.col=col;
    }
}
  1. 由于BFS是通过层次的顺序来遍历的,因此可以用来求最小步数