Algs4-1.4.34B热还是冷-lgN
1.4.34热还是冷。你的目标是猜出1到N之间的一个秘密的整数。每次猜完一个整数后,你会知道你的猜测和这个秘密整数是比较热(接近)还是比较冷(远离)。设计一个算法在~2lgN之内找到这个秘密整数,然后再设计一个算法在~1lgN之内找到这个秘密整数。
答:解决2lgN中的不得不在每一次猜两个数就实现了lgN要求。 要达到最多使用lgN次猜数就可以猜出秘密数,那么要确保每一次猜数都能将秘密数所在的区间减半。
算法:
为了确保通过每一个猜数的温变信息能将秘密数所在区间减半,只要以秘密数所在区间的中点作为对称点,将当前猜数的对称数作为新的猜数即可。但新的猜数有时会不在1~N区间,如果题意要求猜数只能是1~N的数,不适用此算法。
初始时秘密数区间为1~N,先猜1,秘密数是1时猜中,不是1时再猜N。秘密数是N时猜中,秘密数即不是1也不是N时,如果N相对1变冷说明秘密数离1近,那么秘密数在1~N/2区间,如果变热说明秘密数离N近,那么秘密数在N/2~N区间,由此将秘密数所在的区间减半,如果即不变冷也不变热,说明秘密数在区间的中间。
区间在每一次猜数后缩减,每次只会对左右边界中的一个边界进行调整到达缩减效果,要么调整左边界,要么只调整右边界。变冷时调整的是离当前猜数近的边界,变热时调整的是离当前猜数远的边界。确定新的区间后找到新区间的中点,然后计算出中点与当前猜数的距离,然后再计算出新的猜数。
public class E1d4d34B
{
public static void main(String[] args)
{
int N=Integer.parseInt(args[0]);
int key=Integer.parseInt(args[1]);
StdOut.printf("\nN=%d,key=%d,guesskey=%d",N,key,guessKey(N,key));
}//end main
public static int guessKey(int N,int key)
{
int lo=1;
int hi=N;
int center;
int distance=0;
int g1=lo;
int g2=hi;
if (g1==key) return g1;
while(true)
{
StdOut.printf("key=%d,lo=%d,hi=%d,g1=%d,g2=%d",key,lo,hi,g1,g2);
if (g2==key)
return g2;
else if (Math.abs(g1-key)>Math.abs(g2-key))//hot
{
StdOut.printf(",hot");
if (lo==farBoundary(lo,hi,g2))//变热时调整与当前猜数较远一点的边界
// 由于java中int值 3/2 的结果会是1,当区间在1~2,秘密数为2时,
//猜数在1的左边时变冷需要调整左边界,那么新的左边还是1,造成无限循环。
//所以当新边界值与旧边界值相同时,直接缩减1。
if(lo==(lo+hi)/2)
lo++;
else
lo=(lo+hi)/2;
else
if(hi==(lo+hi)/2)
hi--;
else
hi=(lo+hi)/2;
//
center=(lo+hi)/2;//取新的区间的中心点值
distance=distanceOfGuessToCenter(g2,center);//当前猜数与新区间中心点值的距离
StdOut.printf(",distance=%d\n",distance);
g1=g2;
g2=nextGuess(center, distance, g2);//下一个猜数是g2以中心点对称的数。
}//end if hot
else if(Math.abs(g1-key)<Math.abs(g2-key))//cold
{
StdOut.printf(",cold");
if (lo==nearBoundary(lo,hi,g2))//变冷时调整与当前猜数较近一点的边界
if(lo==(lo+hi)/2)
lo++;
else
lo=(lo+hi)/2;
else
if(hi==(lo+hi)/2)
hi--;
else
hi=(lo+hi)/2;
//
center=(lo+hi)/2;
distance=distanceOfGuessToCenter(g2,center);
StdOut.printf(",distance=%d\n",distance);
g1=g2;
g2=nextGuess(center, distance, g2);
}//end if cold
else
{
g1=g2;
g2=(lo+hi)/2;
StdOut.printf(",not change\n");
}
}//end while
}//end guessKey
public static int nearBoundary(int lo,int hi,int guess)
{
if(Math.abs(guess-lo)<Math.abs(guess-hi))
return lo;
else
return hi;
}
public static int farBoundary(int lo,int hi,int guess)
{
if(Math.abs(guess-lo)<Math.abs(guess-hi))
return hi;
else
return lo;
}
public static int distanceOfGuessToCenter(int guess,int center)
{
return Math.abs(guess-center);
}
public static int nextGuess(int center,int distance,int currentGuess)
{
int nextGuess1=center+distance;
int nextGuess2=center-distance;
if (nextGuess1==currentGuess)
return nextGuess2;
else
return nextGuess1;
}
}//end class
下一个猜数的计算公式推导引起的代码变更:
设:gn为下一个猜数,lo为左边界,hi为右边界,m为区间中点,g为当前猜数(对应代码中的g2),d为g与m的距离。
当g与hi相近时,过程1得出gn=lo+hi-g
当g与lo相近时,过程2得出gn=lo+hi-g
两者相同,将上述代码调整后得到:
public class E1d4d34C
{
public static void main(String[] args)
{
int N=Integer.parseInt(args[0]);
int key=Integer.parseInt(args[1]);
StdOut.printf("\nN=%d,key=%d,guesskey=%d",N,key,guessKey(N,key));
}//end main
public static int guessKey(int N,int key)
{
int lo=1;
int hi=N;
int g1=lo;
int g2=hi;
if (g1==key) return g1;
while(true)
{
StdOut.printf("key=%d,lo=%d,hi=%d,g1=%d,g2=%d",key,lo,hi,g1,g2);
if (g2==key)
return g2;
else if (Math.abs(g1-key)>Math.abs(g2-key))//hot
{
StdOut.printf(",hot\n");
if (lo==farBoundary(lo,hi,g2))
// 由于java中int值 3/2 的结果会是1,当区间在1~2,秘密数为2时,
//猜数在1的左边时变冷需要调整左边界,那么新的左边还是1,造成无限循环。
//所以当新边界值与旧边界值相同时,直接缩减1。
if(lo==(lo+hi)/2)
lo++;
else
lo=(lo+hi)/2;
else
if(hi==(lo+hi)/2)
hi--;
else
hi=(lo+hi)/2;
//
g1=g2;
g2=nextGuess(lo, hi, g2);//下一个猜数是g2以中心点对称的数。
}//end if hot
else if(Math.abs(g1-key)<Math.abs(g2-key))//cold
{
StdOut.printf(",cold\n");
if (lo==nearBoundary(lo,hi,g2))//变冷时调整与当前猜数较近一点的边界
if(lo==(lo+hi)/2)
lo++;
else
lo=(lo+hi)/2;
else
if(hi==(lo+hi)/2)
hi--;
else
hi=(lo+hi)/2;
//
g1=g2;
g2=nextGuess(lo, hi, g2);
}//end if cold
else
{
g1=g2;
g2=(lo+hi)/2;
StdOut.printf(",not change\n");
}
}//end while
}//end guessKey
public static int nearBoundary(int lo,int hi,int guess)
{
if(Math.abs(guess-lo)<Math.abs(guess-hi))
return lo;
else
return hi;
}
public static int farBoundary(int lo,int hi,int guess)
{
if(Math.abs(guess-lo)<Math.abs(guess-hi))
return hi;
else
return lo;
}
public static int nextGuess(int lo,int hi,int currentGuess)
{
return lo+hi-currentGuess;
}
}//end class
以上代码冷热部分稍作调整后可以将相同代码合并。
参考资料:
1)http://stackoverflow.com/questions/25558951/hot-and-cold-binary-search-game
2)http://www.ithao123.cn/content-10018711.html