一、问题
把正整数1,2,3…n组成一个环,使得相邻两个整数之和为素数,输出时从整数1开始逆时针排列。n不大于16。
二、思路
最直接的方式是列出所有的素数排列,然后逐个判断是否满足要求,这种方式很简单好理解,但是问题在于所有的素数列量太大,速度会很慢。从另一个角度来看,7.2,7.3,7.4本质上都是广义上的搜索问题,而且都是可以分步骤用递归解决的。本质上,这种解决方法都是在解答树上通过深度优先搜索出符合要求的节点。对于7.3,所有的节点都包含符合要求的答案。对于7.2和7.4,这些包含答案的节点都是叶子节点,不同的是7.2中的每个叶子节点都是答案,而7.4由于题目要求的严格,很过叶子节点都不满足要求因此不是答案。更进一步的,我们很自然的可以想到在搜索的过程中,有时候实际上不用走到叶子节点就知道“此路不通”了,例如给定5个数,如果选择了第一个数为1,且第二个数为3的组合,不用看第三个数就知道肯定无法构成素数环了,因此也不用继续增加深度了,而是调转头来(backtrace,即回溯)考虑第二个数的其他可能性。
三、代码
1. 伪代码
curr:当前步数,初始化为1 A:当前已经存放了curr个数字的数组,初始化A[0]=1 不变逻辑: a. 如果curr==n,判断最后一个数和第一个数的和是否为素数,是的话打印A,否则返回 b. 对于2~n中的每个数字,如果没有出现在A中,并且和A[curr-1]的和为素数,则放入A[curr],curr++,进入下一步。 递归边界:curr == n
2.代码
public class Backtrace {
private static Integer N = 16;
public static void main(String[] args) {
Integer[] output = new Integer[N];
output[0] = 1;
primeNumberCircle(output,1);
}
private static void primeNumberCircle(Integer[] A, Integer curr){
if(curr==N){
//检查第一个数和最后一个数的和是否为素数
if(isPrime(A[0]+A[N-1])){
for(int i=0; i<N; i++){
System.out.print(A[i]);
System.out.print(" ");
}
System.out.println();
}
return;
}
// 从2到N直接选出可以放在当前位置的数
for(int j=2;j<=N;j++){
//要求在A中还没有出现
Boolean found=false;
for(int i=0; i<curr; i++){
if(A[i]!=null && A[i].equals(j)){
found=true;
break;
}
}
//同时要求和前一个数字的和为素数
if(!found){
if(isPrime(A[curr-1]+j)){
A[curr]=j;
primeNumberCircle(A,curr+1);
}
}
}
}
private static Boolean isPrime(Integer n) {
for (int i = 2; i <= Math.sqrt(n); i++) {
if (n % i == 0) return false;
}
return true;
}
}
四、总结 1. 递归一定有的:一个记录中间结果的数组A,一个记录层次深度的变量curr 2. 回溯法的关键是寻找合适的剪枝条件