竞赛算法–深入递归(上)(DFS、回溯、剪枝等)
竞赛算法–深入递归(中)(DFS、回溯、剪枝等)
3、回溯和剪枝
回溯:
- 递归调用代表开启一个分支,如果希望这个分支返回后某些数据恢复到分支开启前的状态 以便重新开始 ,就要用到回溯技巧
- 全排列的交换法,数独,部分和,用到了回溯.
剪枝:
例题:
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;
}
}