整数i的两种变换定义为 , f(i)=3*i,g(i)=i/2(向下取整);
设计一个算法求给定两个整数a和b,用最少次数的 和 变换将整数a变换为b;例如 4=gfgg(15)
提示:
观察f和g两个操作可知,f总是使得i变大,g总是使得i变小。因此在决定让x执行哪个操作之前可以先判断i和目标值m之间的大小关系。如果x>m,就让其执行g操作;反之,执行f操作。
问题的解分为两种情况,一种是有解,即n可以通过函数变换成m;另一种是无解,即n无法通过函数变换成m。
有解的情况比较容易,只需要判断最后的i是否等于m即可。如果i等于m,那么说明n已经被变换成m了,递归返回。
无解的情况可用下例分析。假设我们的输入n=9,m=5。
n>m,执行g,n=[9/2]=4
n<m,执行f,n=3*4=12
n>m,执行g,n=[12/2]=6
n>m,执行f,n=[6/2]=3
n<m,执行g,n=3*3=9
n>m,执行f,n=[9/2]=4
如果n的值陷入了一个重复的循环,如果在递归的过程中,出现了前面计算过的元素,那就说明n是无法转换成m的。这种方法实现稍微复杂,需要判断当前所求出的数值之前是否出现过。
另一种简单的处理方式: 对于m无论如何变换都不能变为n的情况,可以加一个判断条件,比如深度达一个较大值为止(如1000)。
回溯法, 用子集树实现,子集树结构为:
回溯返回条件有两个,一个是i等于m,另一个是出现了重复的数字。第二个返回条件可以用一个函数test来判断。
剪枝条件:
显式约束:如果x>m,就剪掉它的左子树;如果x<m,就剪掉它的右子树;
隐式约束:如果在某次计算的过程中发现当前的计算次数已经大于或等于最少计算次数了,那么就剪掉这个分支。
import java.util.Scanner;
public class sy5_1 {
static int m,n1; //n1方便后面的输出形式
static int tempcount,bestcount;//当前变换次数,最佳变换次数
static int[] temp1=new int[100];
static int[] temp2=new int[1000];
public static void main(String args[]){
Scanner input=new Scanner(System.in);
System.out.println("请输入需变换的整数");
int n=input.nextInt();
n1=n;
System.out.println("请输入需变换后的整数");
m=input.nextInt();
tempcount=0;
bestcount=100;
test(n);
}
//实现方法
public static void test(int n){
if(tempcount>99){ //最好的变换次数已经大于某个特定的值,是无解情况(隐式约束)
System.out.println("无解");
}
else{
if(n==m){ //有解情况
if(tempcount<bestcount) //
bestcount=tempcount;
System.out.println("最佳变换次数:"+bestcount);
System.out.printf("%d=",m);
for(int i=bestcount;i>=1;i--){
if(temp2[i]==2)
System.out.print("f");
if(temp2[i]==1)
System.out.print("g");
}
System.out.printf("(%d)",n1);
}
else if(n>m){ //n>m走这个分支 ,起到剪枝作业
n=g(n);
tempcount++;
temp2[tempcount]=1;
test(n);
}
else { //n<m走这个分支
n=f(n);
tempcount++;
temp2[tempcount]=2;
test(n);
}
}
}
//f(i)=3*i
public static int f(int n){
n=3*n;
return n;
}
//g(i)=i/2
public static int g(int n){
n=n/2;
return n;
}
}
实验结果: