竞赛算法--深入递归(下)(DFS、回溯、剪枝等)

竞赛算法–深入递归(上)(DFS、回溯、剪枝等)

竞赛算法–深入递归(中)(DFS、回溯、剪枝等)

3、回溯和剪枝

回溯:
剪枝:
  • 深搜时,如已明确从当前状态无论如何转移都不会存在(更优)解,就应该中断往下的继续搜索,这种方法称为剪枝
  • 数独里面有剪枝
  • 部分和里面有剪枝
  • if ( 限定条件) { dfs( ); }

例题:
3.1 、 n皇后问题
3.2 、 素数环 (算法竞赛入门经典)
3.3 、 困难的串(算法竞赛入门经典)

3.1 、 n皇后问题

题目描述:

  • 请设计一种算法,解决著名的n皇后问题。
  • 这里的n皇后问题是指在一个n*n的棋盘上放置n个棋子
  • 使得每行每列和对角线上只有一个棋子,求其摆放的方法数。
  • 给定一个 int n , 请返回方法数,保证 n 小于等于15

题目分析+题解代码:
在这里插入图片描述伪代码分析:

int  n;
int count=0;
int[]rec;
dfs(1);
dfs(rec , row){   // 行
    if(row = n){
        count++;
        return;
    }
    for(col  1--> n ){
        if(check(rec , row ,col)){
            rec[row] = col;  // 假设row这一行把皇后放在col这列
            dfs(rec , row+1)
            rec[row] = 0;
        }
    }
}
check(rec , x , y){
    for(int i =0 ;i< rec.length; ++i){
        if(rec[i] == y) return false;
        if(i + rec[i] == x+y)  return false;
        if(i - rec[i[ == x-y)  return false;
    }
    return true;
}

完整代码:

import java.util.Scanner;
public class Main{
    int n ; 
    int count ;
    int [] rec ;
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        rec = new int [n];
        System.out.println(count);
    }
    private static void dfs(int row){
        if(row == n){ // row为正在处理的行
            count++;
            return ; 
        }
    }
    //依次尝试在某列上放一个皇后
    for( int col = 0; col < n;col++){
        boolean ok = true;
        // 检验这个皇后是否和之前已经放置的皇后有冲突
        for(int i=0;i<row;i++){
            if(rec[i] == col || i+rec[i] == row+col || rec[i]-i == col - row ){
                ok = false;
                break;
            }
        }
        /* ======  这里可以认为是剪枝 ===== */
        // 这一行的这一列可以放
        if(ok){
            rec[row] = col;  // 标记
            dfs(row+1); // 继续寻找下一行
            rec[row] = 0; // 此行回溯  本题回溯是否都可。
        }
    }
}
3.2 、 素数环

题目描述:

  • 输入正整数n,对 1 – n 进行排列,使得相邻两个数之和均为素数。
  • 输出时从整数 1 开始,逆时针排序。同一个环应恰好输出一次。
  • n <= 16
  • 如输入: 6
  • 输出:
  • 1 4 3 2 5 6
  • 1 6 5 2 3 4

题目分析+题解代码:
伪代码分析:

int [] rec = new int[n];
rec[0] = 1;
dfs(k){
    if(k==n){
        print(rec);  // 输出
        return ;
    }
    for(i from 2 to n){
        if(check(rec , i)){ // i没有在rec中出现过  , i+rec[k-1]是一个素数
            rec[k] =i;
            dfs(k+1);
            rec[k] = 0;
        }
    }
}

完整代码:

import java.util.Scanner;
public class Main{
    public static void main(String []args){
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int [] r = new int[n];
        r[0] = 1;
        dfs(n , r, 1);
    }
    private static void dfs(int n , int [] r , int cur){
        if(cur == n && isPrime(r[0] + r[n-1])){ //填到末尾了,还有收尾相加为素数才算成功
            print(r);
            return;
        }
        for(int i=2;i<=n;i++){ // 尝试用每个数字填到cur这个位置
            if(check(r,i,cur)){ // r中没有i这个数,且和上一个数之和为素数
                r[cur] = i; // 试着将i放在cur位置,往前走一步
                dfs(n,r,cur+1);
                r[cur] = 0 ; // 回溯
            }
        }
    }
      private static void print(int [] r){
           for(int i=0;i<r.length;i++){
               System.out.print(r[i]+(i==r.length-1 ? "" : " "))
           }
           System.out.println(); 
       }
       private static boolean check(int [] r , int i , int cur){
           for(int e : r){
               if(e == i || !isPrime(r[cur-1]+i))  return false;
           }
           return true;
       }
       private static boolean isPrime(int k){
           for(int i =2;i*i <= k;i++){
               if(k % i ==0)   return false;
           }
           return true;
       }
}
3.3 、 困难的串

题目描述:

  • 如果有一个字符串包含两个相邻的重复子串,则称它为容易的串,其他串称为困难的串
  • 如:BB , ABCDACABCAB , ABCDABCD 都是容易的串
  • 如:A ,ABA , D , DC , ABDAB , CBABCBA 都是困难的串。
  • 输入正整数 n , L , 输出由前L个字符(大写英文字母)组成的串,字典序第n小的困难的串。
  • 例如: L = 3时,即ABC组成的前7个困难的串分别为:
  • A , AB , ABA , ABAC , ABACA , ABACAB ,ABACABA

题目分析+题解代码:

public class Main{
    public static void Main(String args[]){
        int n = 10;
        int l = 4;
        dfs(l,n,"");
    }
    static int count;
    private static void dfs(int l , int n , String prefix){
        // 尝试在prefix后追加一个字符
        for(char i = 'A' ; i<'A'+l;i++){
            if(isHard(prefix,i)){  // 是困难的串就组合起来
                String x = prefix+i;
                System.out.println(x);
                count++; // 计数
                if(count == n){
                    System.out.println(0);
                }
                dfs(l,n,x);
            }
        }
    }
    /**
    * 判断prefix+i 是否是一个困难的串
    * 1. 遍历所有的长度为偶数的子串,看是否对称
    * 2.prefix是一个困难的串  ABA i 判断i是否和倒数第一位相等,Ai是否和倒数2-3位相等,依次类推
    */
    private static boolean isHard(String prefix , char i){
        int count = 0; // 截取的宽度
        for(int j = prefix.length()-1;j>=0;j=j-2){
            String s1 = prefix.substring(j,j+count+1);
            String s2 = prefix.substring(j + count + 1) + i;
            if(s1.equals(s2))
                return false;
            count++;  // 截取长度依次增加
        }
        return true;
    }
}
发布了52 篇原创文章 · 获赞 7 · 访问量 1802

猜你喜欢

转载自blog.csdn.net/weixin_44107920/article/details/104057636