一个青蛙过河程序及其解析
2009-12-17 11:48
281 查看
近日在CSDN上看到中软一道面试题,挺有意思的。
题目:一条小溪上7块石头,如图所示:
分别有六只青蛙:A,B,C,D,E,F。A,B,C三只蛙想去右岸,它们只会从左向右跳;D,E,F三只蛙想去左岸,它们只会从右向左跳。青蛙每次最多跳到自己前方第2块石头上。请问最少要跳几次所有青蛙上岸。写出步骤。
这个题是个路径搜索的问题,在解空间搜索所有的解,并找出最优的解法(即步骤最少的)。
那么怎么算是一个解呢?具体而言就是最后石头上没有青蛙了。
我们先给题目建模,7块石头,其上可以是没青蛙,可以有一只往左跳的青蛙,也可以有一只往右跳的青蛙。可以把这7块石头看成一个整体,来表示一个状态。这里我们把这7块石头看成一个数组,里面只能有0,1,2三种值,这样表示,那么初始时为:
1
,
1
,
1
,
0
,
2
,
2
,
2
我们把它再表示成一个数字,来表示状态值,这个值把这个数组按三进制拼成一个数字,我们用一个辅助函数来做这件事情:
private
final
int
makeS() {
int
r
=
0
;
int
p
=
1
;
for
(
int
i
=
0
;i
<
7
;i
++
)
{
r
+=
p
*
states[i];
p
*=
3
;
}
return
r;
}
那么题目现在变成从状态111022转换成状态0000000,所需最少的步骤.
那么状态是怎样转换的呢?
很显然。,每次青蛙跳都会触发状态的转换,我们在每个状态时搜索每种可能的转换,我们记初始状态为S(S等于三进制111022)记要求解的值为OPT(S),假如可以转换到t1,t2,...tk.
那么,显然
OPT(S)
=
min(
1
+
OPT(t1),
1
+
OPT(t2),
.,
1
+
OPT(tk));
另外,由于最终状态为0,所以OPT(0)=0,就是说已经在最终状态了,就不需要一步就可以了。
有了上面这个等式,我们可以递归求解了,但是如果单纯的递归,会导致大量的重复计算,所以这里我们用备忘录的方法,记下已经求解出来的OPT(x),放在
一个数组里,由于只有7块石头,所以最多我们需要3^7=2187个状态。我们用一个2187个元素的数组,
其中第i个元素表示OPT(i),初始化每个元素用-1表示还未求解。OPT(0) 可直接初始化为0.
到此我们还有一个问题,怎么能够在算法结束的时候打印出最优的步骤呢?按照这个步骤,我们可以重建出青蛙是如何在最优的情况下过河的。为此,我们可以再用一个步骤数组,每次在采取最优步骤的时候记录下来。
整个算法如下:
package test;
import java.util.Arrays;
/**
*
* @author Yovn
*
*/
public class FrogJump {
private int steps[];
private int states[];
private static class Step
{
int offset=-1;
int jump;
int jumpTo;
}
private Step jumps[];
private int initS;
public FrogJump()
{
steps=new int[81*27];
states=new int[7];
for(int i=0;i<3;i++)states[i]=1;
for(int i=4;i<7;i++)states[i]=2;
Arrays.fill(steps, -1);
steps[0]=0;
jumps=new Step[81*27];
initS=makeS();
}
public int shortestSteps(int s)
{
if(steps[s]==-1)
{
int minStep=Integer.MAX_VALUE;
Step oneStep=new Step();
for(int i=0;i<7;i++)
{
if(states[i]==1)
{
if(i>4)
{
states[i]=0;
minStep = recurFind(minStep,oneStep,i,7-i);
states[i]=1;
}
else
{
if(states[i+1]==0)
{
states[i]=0;
states[i+1]=1;
minStep = recurFind(minStep,oneStep,i,1);
states[i]=1;
states[i+1]=0;
}
if(states[i+2]==0)
{
states[i]=0;
states[i+2]=1;
minStep = recurFind(minStep,oneStep,i,2);
states[i]=1;
states[i+2]=0;
}
}
}
else if(states[i]==2)
{
if(i<2)
{
states[i]=0;
minStep = recurFind(minStep,oneStep,i,-1-i);
states[i]=2;
}
else
{
if(states[i-1]==0)
{
states[i]=0;
states[i-1]=2;
minStep = recurFind(minStep,oneStep,i,-1);
states[i]=2;
states[i-1]=0;
}
if(states[i-2]==0)
{
states[i]=0;
states[i-2]=2;
minStep = recurFind(minStep,oneStep,i,-2);
states[i]=2;
states[i-2]=0;
}
}
}
}
steps[s]=minStep;
jumps[s]=oneStep;
}
return steps[s];
}
private final int recurFind(int minStep, Step oneStep, int pos, int jump) {
int toS=makeS();
int r=shortestSteps(toS);
if(r<minStep-1)
{
oneStep.jump=jump;
oneStep.offset=pos;
oneStep.jumpTo=toS;
minStep=r+1;
}
return minStep;
}
public void printPath()
{
int s=initS;
int i=1;
while(s!=0)
{
System.out.println("["+(i++)+"] Frog at #"+jumps[s].offset+" jumps #"+jumps[s].jump);
s=jumps[s].jumpTo;
}
}
private final int makeS() {
int r=0;
int p=1;
for(int i=0;i<7;i++)
{
r+=p*states[i];
p*=3;
}
return r;
}
/**
* @param args
*/
public static void main(String[] args) {
FrogJump fj=new FrogJump();
int steps=fj.shortestSteps(fj.initS);
System.out.println("use "+steps+" steps!");
fj.printPath();
}
}
运行结果:
use
21
steps
!
[
1
] Frog at #
2
jumps #
1
[
2
] Frog at #
4
jumps #
-
2
[
3
] Frog at #
5
jumps #
-
1
[
4
] Frog at #
3
jumps #
2
[
5
] Frog at #
1
jumps #
2
[
6
] Frog at #
0
jumps #
1
[
7
] Frog at #
2
jumps #
-
2
[
8
] Frog at #
0
jumps #
-
1
[
9
] Frog at #
4
jumps #
-
2
[
10
] Frog at #
2
jumps #
-
2
[
11
] Frog at #
0
jumps #
-
1
[
12
] Frog at #
5
jumps #
2
[
13
] Frog at #
3
jumps #
2
[
14
] Frog at #
1
jumps #
2
[
15
] Frog at #
5
jumps #
2
[
16
] Frog at #
3
jumps #
2
[
17
] Frog at #
5
jumps #
2
[
18
] Frog at #
6
jumps #
-
1
[
19
] Frog at #
5
jumps #
-
2
[
20
] Frog at #
3
jumps #
-
2
[
21
] Frog at #
1
jumps #
-
2
题目:一条小溪上7块石头,如图所示:
分别有六只青蛙:A,B,C,D,E,F。A,B,C三只蛙想去右岸,它们只会从左向右跳;D,E,F三只蛙想去左岸,它们只会从右向左跳。青蛙每次最多跳到自己前方第2块石头上。请问最少要跳几次所有青蛙上岸。写出步骤。
这个题是个路径搜索的问题,在解空间搜索所有的解,并找出最优的解法(即步骤最少的)。
那么怎么算是一个解呢?具体而言就是最后石头上没有青蛙了。
我们先给题目建模,7块石头,其上可以是没青蛙,可以有一只往左跳的青蛙,也可以有一只往右跳的青蛙。可以把这7块石头看成一个整体,来表示一个状态。这里我们把这7块石头看成一个数组,里面只能有0,1,2三种值,这样表示,那么初始时为:
1
,
1
,
1
,
0
,
2
,
2
,
2
我们把它再表示成一个数字,来表示状态值,这个值把这个数组按三进制拼成一个数字,我们用一个辅助函数来做这件事情:
private
final
int
makeS() {
int
r
=
0
;
int
p
=
1
;
for
(
int
i
=
0
;i
<
7
;i
++
)
{
r
+=
p
*
states[i];
p
*=
3
;
}
return
r;
}
那么题目现在变成从状态111022转换成状态0000000,所需最少的步骤.
那么状态是怎样转换的呢?
很显然。,每次青蛙跳都会触发状态的转换,我们在每个状态时搜索每种可能的转换,我们记初始状态为S(S等于三进制111022)记要求解的值为OPT(S),假如可以转换到t1,t2,...tk.
那么,显然
OPT(S)
=
min(
1
+
OPT(t1),
1
+
OPT(t2),
.,
1
+
OPT(tk));
另外,由于最终状态为0,所以OPT(0)=0,就是说已经在最终状态了,就不需要一步就可以了。
有了上面这个等式,我们可以递归求解了,但是如果单纯的递归,会导致大量的重复计算,所以这里我们用备忘录的方法,记下已经求解出来的OPT(x),放在
一个数组里,由于只有7块石头,所以最多我们需要3^7=2187个状态。我们用一个2187个元素的数组,
其中第i个元素表示OPT(i),初始化每个元素用-1表示还未求解。OPT(0) 可直接初始化为0.
到此我们还有一个问题,怎么能够在算法结束的时候打印出最优的步骤呢?按照这个步骤,我们可以重建出青蛙是如何在最优的情况下过河的。为此,我们可以再用一个步骤数组,每次在采取最优步骤的时候记录下来。
整个算法如下:
package test;
import java.util.Arrays;
/**
*
* @author Yovn
*
*/
public class FrogJump {
private int steps[];
private int states[];
private static class Step
{
int offset=-1;
int jump;
int jumpTo;
}
private Step jumps[];
private int initS;
public FrogJump()
{
steps=new int[81*27];
states=new int[7];
for(int i=0;i<3;i++)states[i]=1;
for(int i=4;i<7;i++)states[i]=2;
Arrays.fill(steps, -1);
steps[0]=0;
jumps=new Step[81*27];
initS=makeS();
}
public int shortestSteps(int s)
{
if(steps[s]==-1)
{
int minStep=Integer.MAX_VALUE;
Step oneStep=new Step();
for(int i=0;i<7;i++)
{
if(states[i]==1)
{
if(i>4)
{
states[i]=0;
minStep = recurFind(minStep,oneStep,i,7-i);
states[i]=1;
}
else
{
if(states[i+1]==0)
{
states[i]=0;
states[i+1]=1;
minStep = recurFind(minStep,oneStep,i,1);
states[i]=1;
states[i+1]=0;
}
if(states[i+2]==0)
{
states[i]=0;
states[i+2]=1;
minStep = recurFind(minStep,oneStep,i,2);
states[i]=1;
states[i+2]=0;
}
}
}
else if(states[i]==2)
{
if(i<2)
{
states[i]=0;
minStep = recurFind(minStep,oneStep,i,-1-i);
states[i]=2;
}
else
{
if(states[i-1]==0)
{
states[i]=0;
states[i-1]=2;
minStep = recurFind(minStep,oneStep,i,-1);
states[i]=2;
states[i-1]=0;
}
if(states[i-2]==0)
{
states[i]=0;
states[i-2]=2;
minStep = recurFind(minStep,oneStep,i,-2);
states[i]=2;
states[i-2]=0;
}
}
}
}
steps[s]=minStep;
jumps[s]=oneStep;
}
return steps[s];
}
private final int recurFind(int minStep, Step oneStep, int pos, int jump) {
int toS=makeS();
int r=shortestSteps(toS);
if(r<minStep-1)
{
oneStep.jump=jump;
oneStep.offset=pos;
oneStep.jumpTo=toS;
minStep=r+1;
}
return minStep;
}
public void printPath()
{
int s=initS;
int i=1;
while(s!=0)
{
System.out.println("["+(i++)+"] Frog at #"+jumps[s].offset+" jumps #"+jumps[s].jump);
s=jumps[s].jumpTo;
}
}
private final int makeS() {
int r=0;
int p=1;
for(int i=0;i<7;i++)
{
r+=p*states[i];
p*=3;
}
return r;
}
/**
* @param args
*/
public static void main(String[] args) {
FrogJump fj=new FrogJump();
int steps=fj.shortestSteps(fj.initS);
System.out.println("use "+steps+" steps!");
fj.printPath();
}
}
运行结果:
use
21
steps
!
[
1
] Frog at #
2
jumps #
1
[
2
] Frog at #
4
jumps #
-
2
[
3
] Frog at #
5
jumps #
-
1
[
4
] Frog at #
3
jumps #
2
[
5
] Frog at #
1
jumps #
2
[
6
] Frog at #
0
jumps #
1
[
7
] Frog at #
2
jumps #
-
2
[
8
] Frog at #
0
jumps #
-
1
[
9
] Frog at #
4
jumps #
-
2
[
10
] Frog at #
2
jumps #
-
2
[
11
] Frog at #
0
jumps #
-
1
[
12
] Frog at #
5
jumps #
2
[
13
] Frog at #
3
jumps #
2
[
14
] Frog at #
1
jumps #
2
[
15
] Frog at #
5
jumps #
2
[
16
] Frog at #
3
jumps #
2
[
17
] Frog at #
5
jumps #
2
[
18
] Frog at #
6
jumps #
-
1
[
19
] Frog at #
5
jumps #
-
2
[
20
] Frog at #
3
jumps #
-
2
[
21
] Frog at #
1
jumps #
-
2
相关文章推荐
- 青蛙过河程序及其解析
- 一个C++的 XML 解析程序
- 一个解析配置文件的小程序
- C++反汇编揭秘1 – 一个简单C++程序反汇编解析 (Rev. 3)
- 分享给大家一个嵌入式linux驱动的入门程序解析(源代码)
- 一个简单的linux shell程序示例及其说明
- 关于王爽老师实验实验八 (分析一个奇怪的程序)之个人解析
- C++反汇编揭秘1 – 一个简单C++程序反汇编解析 (Rev. 3)(转)
- DOM4J解析XML 写的一个小程序总结
- C++反汇编揭秘1 – 一个简单C++程序反汇编解析 (Rev. 3)
- C++反汇编揭秘1 一个简单的C++程序反汇编解析
- 一个能够解析各种程序语言并且设定颜色上去的工具
- 编写一个小程序,从标准输入读入一系列string对象,寻找连续重复出现的单词。程序应该找出满足一下条件的单词:该单词的后面紧接着再次出现自己本身。跟踪重复次数最多的单词及其重复次数,输出.
- 今天做的一个ruby解析xml的小程序
- 编写程序定义一个vector 对象,其每个元素都是指向string 类型的指针,读 取该vector 对象,输出每个string 的内容及其相应的长度。
- 贴一个刚写的 词法解析程序吧(C语言),很简陋,呵呵。。。
- 程序及其运行时内存分配解析
- 一个简单的利用msxml解析xml的程序
- 一个SAX解析xml的简单程序。
- 有一个南北向的桥,只能容纳一个人,现桥的两边分别有10人和12人,编制一个多线程序让这些人到达对岸,每个人用一个线程表示,桥为共享资源。在过桥的过程中显示谁在过桥及其走向。