汉诺塔问题1与2:若arr是汉诺塔最优走法的其中一步,请返回这是第几步?
提示:汉诺塔问题有2个,一个正向理解,一个反向解题
这俩都要会
1简单,2难,但是1也是理解2的关键所在
汉诺塔问题1
请你把N层汉诺塔,按照最优解,把所有盘子,从1号棍子搬到3号棍子,依次输出搬移顺序;
小号盘子不能被压在大号盘子下面,最优解意味着最大号那个盘子,它不能去别的棍子上绕,
当它是最大号时,必须直接从1号棍子直接搬到3号棍子
【即从起始点1号棍子,直接搬到终点3号棍子,不能去2号棍子上绕——最优解】
一、审题
示例:不妨设N=3,即3层汉诺塔问题,这是最基本的例子,看图
让你,把3层汉诺塔,编号为012这仨盘子,想办法合法搬移,从1号棍子挪到到3号棍子上。
二、解题
汉诺塔的搬移原则:
起点是from棍子【如本题中1号】,
终点是to棍子【如本题中3号】,
另外一根棍子让位用的叫other棍子【如本题中2号】
搬移i层汉诺塔【编号为0—i-1】,我们定义这一样一个搬移函数:
f(i, from, to, to),把i层汉诺塔从from,搬到to,中间other做让位:
如果是3层汉诺塔:则最大号盘子叫i-1=2号盘,即012
现在:from=1,to=3,other=2
第1步:将0号盘从1搬到3
第2步:将1号盘从1搬到2
第3步:将0号盘从3搬到2
——注意,这几步干了什么?将01两个盘,转移到让位other=2上
第4步:将2号盘从1搬到3
——注意,这一步干了什么?将2号盘,从from=1搬到了to=3上,这就是N层汉诺塔最大这个盘子
其实就是先让N-1层汉诺塔,让位到other上,我再搬最大这个盘子
第5步:将0号盘从2搬到1
第6步:将1号盘从2搬到3
第7步:将0号盘从1搬到3
——注意:这几步干了什么?将01两个盘,转移到to上,最终完成汉诺塔问题
总结汉诺塔,要把0–i-1搬到目的地,需要干这三个大步骤:
是这样搬的,大逻辑:i层汉诺塔,
(1)第一个大步骤:将i-1层汉诺塔先让位,从from转移到other上;
(2)第二个大步骤:现在from上只剩下i了,把i从from转移到to上【这是最终目的,也是最优解】;
(3)第三个大步骤:然后将让位那i-1层汉诺塔,将其从other上转移到to;完成i层汉诺塔的事情
发了案例中用了几步吗?
3层汉诺塔,用了7步,每一步都走最优解的话,那我们步数就是2^i-1步【这个我们在汉诺塔问题2会用的】
好,看懂了案例,也知道了汉诺塔移动最优解的原则之后,我们来设计f函数:
process=f
//现在,这样看,将起点定位from,终点定位to,将让路的点定位other
//终止条件还是N==1,将from直接移动到to
//其余的,都是相互变换角色
//先将N-1个盘,从from移动到other处,让路
//然后将N从from移动到to
//最后,将N-1个盘从other移动到to
//完成汉诺塔
public static void hanNuoTa2(int N){
if (N <= 0) return;
process(N, "左", "右", "中间");
}
public static void process(int N, String from, String to, String other){
//终止条件
if (N == 1) {
System.out.println("将 1 号盘,从"+ from +"移动到"+ to);
return;
}
//否则先让N-1个盘让路到other,然后把N从from移动到to,再让N-1个盘从other处移动到to
process(N - 1, from, other, to);
System.out.println("将 "+ N +" 号盘从"+ from +"移动到"+ to);
process(N - 1, other, to, from);
}
public static void test2(){
hanNuoTa2(2);
}
public static void main(String[] args) {
test2();
}
解释一下,N=1就输出是我们将N层汉诺塔编号叫123—N
等同编号为012–N,一个道理
无非就是写0号盘子开头,或者写1号盘子开头而已!
好,现在我们可以简单地将一个n层汉诺塔最优步数用递归算一下:
//不妨设:f(i,from,to,other)是将0--i号盘全部从from搬移到to,利用other中转,最后走的步数
public static int f(int i, int from, int to, int other){
if (i == -1) return 0;//没有盘子了,不走,返回0步
//否则还有盘子,0号盘是最小盘子
int step1 = f(i - 1, from, other, to); //走第1)步
int step2 = 1;//完成2)
int step3 = f(i - 1, other, to, from);//完成3)
return step1 + step2 + step3;
}
//汉诺塔问题1的调用方式
public static int bestStepHanNuo(int N){
if (N == 0) return 0;//没有盘子不走
if (N == 1) return 1;//一个盘子,走一步到位
return f(N - 1, 1, 3, 2);//将0--N-1号盘子从1搬到3,将2作为中转,最优解是多少步???
}
汉诺塔问题2
给你一个arr数组,每一个arr[i]代表棍子的编号【最简单的3根棍子,编号123】,意味着i号盘子,现在正在arr[i]棍子上。
这个arr显然就是代表汉诺塔搬移过程中的某一个步骤【状态】,
如果arr是汉诺塔问题最优走法的其中一步,请问你这是第几步?
返回arr是最优走法的第几步,如果不是最优走法的步骤,请返回-1。
三、审题
有了汉诺塔问题1,就很容易理解汉诺塔问题2
示例:arr=1111111——3333333
最开始都在1号棍子上,0–6盘子,显然这个arr=111111是汉诺塔问题的第0步;
最后搬到to上之后,都在3号棍子上,3333333这个arr是汉诺塔问题的第2^7-1步
四、解题
再看例子:3层汉诺塔问题,盘子编号为012
arr=111,表示汉诺塔问题最开始状态都在1棍子上
arr=311,表示汉诺问题第1步,0号盘放到了3棍子上
arr=321,表示汉诺塔问题第2步,1号盘放到了2棍子上
……
那么现在来到了i号盘,我们让i盘子去哪里?
如果是最优解,我们既然已经让i-1层汉诺塔让位到other了,你i就直接去to吧,这就是最优解,你i非要去other绕弯子,没卵用,返回-1,不合法的。
现在我们定义一个函数f(arr,i,from,to,other):arr状态带着,i+1层汉诺塔问题,0–i为编号,把i+1层全部搬到to上,看看我arr是最优解的第几步?
此时来看i号盘在哪呢?
(1)如果i号盘在other上,不好意思,违规,return -1;
(2)如果i号盘在from上,这说明压根汉诺塔的第1步还没有完成,直接返回f(arr,i-1,from,other,to);
——啥意思?也就是说,你i都还在from上,显然此时你在第几步?你在将i-1层汉诺塔让位到other上,步骤就只需要算汉诺塔问题的第1步那么多,直接返回f(arr,i-1,from,other,to)。看清楚,这里的目的地是让位other!
(3)如果i号盘在to上,说明你干完了所有汉诺塔问题的3步,即让位i-1层,把i放到to(1步),然后把让位那i-1层搬到to上,故返回:
2^(i)+1+f(arr,i-1,other,to,from)。看清楚,这里的真实目的地是to,起点是other!
为啥是i次方,因为0–i-1编号就i层汉诺塔
注意:f(arr,i-1,other,to,from)=-1时,无效!!!
主函数怎么调用,我们要调N-1号开始的汉诺塔问题,因为我们是0–N-1编号的
遇到i==-1,说明没有盘子了,那返回0步就行
//好,有了汉诺塔1的解,现在,通过arr表示汉诺塔了,arri可以表示i号盘在哪个位置,明白了吗,i号盘作为下标,它所在的位置是arri
//这不就很好办了吗,我们要看违规的情况,
//如果arri在other,显然违规,不是最优解
//如果arri在from,显然,它还需要完成1),然后2)才能去完成3)
//如果arri在to,显然2)都已经完成了,需要完成3)然后统计步数返回
//记住n层汉诺塔问题最优解步数是2^n-1步
//不妨设f2(arr,i,from,to,other)0--i汉诺塔问题要把它从from搬到to,请问如果arr是其中一步的话,返回步数,否则违规就返回-1
public static int f2(int[] arr, int i, int from, int to, int other){
if (i == -1) return 0;//没盘子了
if (arr[i] != from && arr[i] != to) return -1;//i来到的位置既不是from也不是to就是other呗,违规,返回-1
else if (arr[i] == from) return f2(arr, i - 1, from, other, to);//i竟然还在from,说明这1)也没有搞定,需要先搞定1)
else {
//arr[i] == to,2)都已经搞定了,还需要搞定3)
int step3 = f2(arr, i - 1, other, to, from);
if (step3 == -1) return -1;//如果搞定3)也违规,返回-1
return (1 << i) - 1 + 1 + step3;
//(1 << i) - 1是0--i-1号盘子,i层哦,所以最优解是2^i - 1
//+1,是搞定2)
//step3是搞定步骤3
}
}
public static int bestStepHanNuo2(int[] arr){
if (arr == null || arr.length == 0) return 0;
int N = arr.length;//N层汉诺塔,下标是0--N-1
return f2(arr, N - 1, 1, 3, 2);
}
测试:
public static void test(){
int[] arr = {3,2,1};//我已经搬好了,这样的话,步数最优解一定是和汉诺塔问题1一样的
System.out.println(bestStepHanNuo(arr.length));
System.out.println(bestStepHanNuo2(arr));
}
public static void main(String[] args) {
test();
}
总结
提示:重要经验:
1)汉诺塔问题1要熟悉原理
2)汉诺塔问题2要知道i在哪个盘上,判定此时处于哪个步骤上,干了汉诺塔问题1的哪几个步骤?