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(" N=%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 ",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 ",distance);
         g1=g2;
         g2=nextGuess(center, distance, g2);
      }//end if cold
   else
      {
        g1=g2;
        g2=(lo+hi)/2;
        StdOut.printf(",not change ");
      }
    }//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(" N=%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 ");
        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 ");
        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 ");
      }
    }//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

图片

图片



原文地址:https://www.cnblogs.com/longjin2018/p/9854522.html