《编程之美》读书笔记(一):中国象棋将帅问题

  
  千呼万唤始出来,在跳票了快一个月之后,虽然明知道书里还有不少错误没改过来(附了一整页的勘误),但是感觉已经不能等下一版了。赶快去书店买回来,吃完饭躺床上舒舒服服地看。大致翻看之后,总体感觉是书中的内容没有“脱离群众”,很多都是我们平时生活、工作中经常能遇到的。题目不见得难,基本上给一本《算法导论》和足够的时间,大多数人都能解决其中的问题。但注意副标题--“微软技术面试心得”,这就给这本书定下一个基调:面对这些我们并不陌生、也并非特别困难的问题,在有限的时间里,(可能)比较紧张的心情之下,如何充分发挥自己分析问题和解决问题的能力,如何正确且漂亮地解决问题才是关键。我想,在平时学习的时候或许我们左手《算法导论》,右手《编程之美》效果会更好一些。

  中国象棋将帅问题由于比较简单,所以我们暂时不用请出《算法导论》。该问题的具体描述是:(根据中国象棋的基本原则)在只有双的将帅棋盘上,找出所有双方可以落子的位置(将帅不能碰面),但只能使用一个变量。直觉上我们想到,只要遍历将帅所有可能的位置,去除将帅冲突的位置即可。可见,剩下的问题就在于如何使用一个变量来做二重循环的遍历。书中解法一给出的方法是将一个Byte变量拆成两个用,前一半代表“帅”可以走的位置,后一个变量代表“将”可以走的位置(事先已经将“将”和“帅”可以走的3*3的位置进行了编号),利用位操作即可获得两个计数器的功能。书中的解法三采用结构体来解决一个变量遍历二重循环的问题,思想上换汤不换药。真正有趣的是解法二,它的代码如下:

  int var = 81;
  while( var-- )
  {
    if( var / 9 % 3 == var % 9 % 3 )//发生冲突
     continue;
    else
 printf(/** 打印可行的位置 **/);
  }

  当看到这个解法的时候,我心里有一些感慨。在前几个月,我一直未MSRA面试没通过而恼火。但看到这个解法之后,我觉得我确实还要再努力一些才行。短短几行,体现了简约之美,仅看看这个就值回钱了(开玩笑)。虽然可能有牛人说这没什么了不起,但我觉得如果我在面试这个问题的时候能写下这样的代码,我会很有成就感。在大多数时候我们无需知道希尔排序的时间复杂度的一点几次方是怎么算出来的,也无需去证明一个最优化问题是否满足“拟阵”的条件,我们只需要在这样一个“简单”的问题上做得漂亮,就够了。

  回过头来分析这个解法。“将”和“帅”各在自己的3*3的格子间里面走动,我们共需要验证9*9=81种位置关系,这也是i=81的由来。此外我们要明白i/9和i%9的含义。我们知道,整数i可以由部两分组成,即var=(var/9)*9+var%9 ,其中var<n。我们注意到,在i从81到0变化的过程中,var%9的变化相当于内层循环,var/9的变话相对于内层循环。这样,作者就妙地用一个变量i同时获得了两个变量的数值。

  简单即是美,相对于解法一的大段代码,我更希望我以后再面试中写出解法二。
 
  其实这个问题还可以进行一些扩展,即如何利用一个变量达到三重循环的效果。也就是说,如果给定下面的循环:

  int counter = 0;
  for( int i = 0; i < 5; i++ )
 for( int j = 0; j < 4; j++ )
   for( int k = 0; k < 3; k++ )
   {
    System.out.println("counter="+counter+" , i="+i+", j="+j+", k="+k);
    counter++;
   }
   其结果如下:
 counter=0 , i=0, j=0, k=0
 counter=1 , i=0, j=0, k=1
 counter=2 , i=0, j=0, k=2
 counter=3 , i=0, j=1, k=0
 counter=4 , i=0, j=1, k=1
    ....中间略
    counter=59  , i=4, j=3, k=2
  
   问题是(1)我们如何用一个打印出相同的结果?(2)如果是N重循环呢?
  
   面对第一个问题,实际上就是对原始的中国象棋将帅问题进行了一个扩展,即在棋盘上添加一个“王”,其行走规则和将帅 一样。于是棋盘变成了三国争霸:-) ,将帅王可以走动的格子数分别为3、4、5,它们之间的互斥条件可以按需要设定。
  
   这时,就需要只用一个变量遍历一个三重循环。直观的方法是像方法一那样把一个4字节的INT拆开来用。我这里只关注方法二。
  
   只用一个变量解决扩展的中国象棋将帅问题,我们的代码应该是如下的样子:
   int var = 3*4*5;
   while( var-- )
   {
     if( /** 冲突条件 **/ )//发生冲突
      continue;
     else   
         printf(/** 打印可行的位置 **/);
   }

   在冲突条件中,我们需要知道var取得某个特定的值(即第var+1次循环)的时候的i,j,k分别是多少(这样我们才能判定将帅位置是否冲突)
  
   从上例的结果中我们可以看到,counter的值(即当前的循环次数)和三元组(i,j,k)是一一对应的,越是外层的循环变化越慢,他们满足什么关系呢?
  
   k的取值最好确定,我们都知道是var%3。
   在原始的将帅问题中我们知道,j的值应该是 var/3,但是由于j上面还有一层循环,就需要做些调整,变成var/3%4
   最外层循环i的值则为(var/(3*4))%5.
    
     即:k=var%3      //其下没有循环了
             j=var/3   //其下有几个循环长度为3的循环
            i=var/(3*4). //其下有几个循环长度为3*4的循环
 
  于是4重循环的公式我们也可以轻松得出:
  for( int i = 0; i < 5; i++ )
     for( int j = 0; j < 4; j++ )
       for( int k = 0; k < 3; k++ )
         for( int p = 0; p < 3; p++ )
   
   p=var%2 //其下没有循环了
   k=var/2      //其下有几个循环长度为2的循环
   j=var/(2*3)) //其下有几个循环长度为2*3的循环
   i=var/(2*3*4)//其下有几个循环长度2*3*4的循环
  
   下面就是一个变量实现三重循环

 

int var = 2*3*4*5;
while( var-- > 0)...{
 System.out.println("var="+var+" , i="+((var/(2*3*4))%5)+
                                                    ", j ="+((var/(2*3))%4)+",
                                                       k="+((var/2)%3)+",
                                                       p="+var%2);
}

  
   结果是:
   var=119 , i=4, j=3, k=2, p=1
   var=118 , i=4, j=3, k=2, p=0
   var=117 , i=4, j=3, k=1, p=1
   ...中间略
   var=5 , i=0, j=0, k=2, p=1
   var=4 , i=0, j=0, k=2, p=0
   var=3 , i=0, j=0, k=1, p=1
   var=2 , i=0, j=0, k=1, p=0
   var=1 , i=0, j=0, k=0, p=1
   var=0 , i=0, j=0, k=0, p=0  
  
   N重循环原理也是一样,就不再赘述了。
  
   PS:看到最后一例的结果是不是与《算法导论》中平摊分析一章的二进制计数器很像?只不过这里进制不一样而已:-)
 
  [勘误: P19 代码清单1-7的第七行,应该改为if(i.a%3 != i.b%3)]

  谨以此文与大家共勉 2008/04/05

时间: 2024-11-05 18:47:45

《编程之美》读书笔记(一):中国象棋将帅问题的相关文章

《JavaScript DOM 编程艺术》读书笔记之JavaScript 简史_javascript技巧

JavaScript 是Netscape公司与Sun公司合作开发的.在 JavaScript 1.0发布时,Netscape Navigator主宰着浏览器市场.微软在推出IE3的时候发布了自己的VBScript语言,同时以JScript为名发布了JavaScript 的一个版本,很快赶上了 Netscape 的步伐.面对微软公司的竞争,Netscape 和 Sun公司联合ECMA(欧洲计算机制造商协会)对JavaScript 语言进行了标准化,于是出现了ECMAScript语言,这是同一种语言

《JavaScript DOM 编程艺术》读书笔记之DOM基础_javascript技巧

DOM              DOM:文档对象模型: 节点        元素节点:DOM的原子是元素节点.<body>.<p>.<ul>之类的元素.元素可以包含其他的元素.没有被包含在其他元素里的唯一元素是<html>元素        文本节点:在XHTML文档里,文本节点总是被包含在元素节点的内部.        属性节点:属性节点用来对元素做出更具体的描述.例如,几乎每个元素都有一个title属性,而我们可以利用这个属性对包含在元素里的东西作出准

《JavaScript DOM 编程艺术》读书笔记之JavaScript 语法_javascript技巧

注释         单行注释://         多行注释:/* */         "<!--"可以用作单行注释,由于和HTML的"<!--  -->"多行注释类似,容易混淆,所以不建议这种注释方法 变量        在JavaScript 语言里,变量和其他语法元素的名字都是区分字母大小写的.名字mood的变量与名字是Mood.MOOD或mOOd的变量没有任何关系,它们不是同一个变量.        JavaScript 语法不允许变量

1.2中国象棋将帅问题

#include <stdio.h> //#include <windows.h> 包含byte #include <IOSTREAM> using namespace std; #define HALF_BIT_LENGTH 4 #define FULLMASK 255 #define LMASK (FULLMASK << HALF_BIT_LENGTH) #define RMASK (FULLMASK >> HALF_BIT_LENGTH)

位运算枚举解决象棋将帅问题

引子问题:在一把象棋的残局中,象棋双方的将帅不可以相见,即不可以在中间没有其他棋子的情况下在同一列出现.而将.帅各被限制在己方的3*3的格子中运动.相信大家都非常熟悉象棋的玩法吧,这里就不详细说明游戏规则了. 用A.B代表将和帅,请写出一个程序,输出A.B所有合法的位置.要求在代码中只能用一个变量. 中国象棋将帅问题: 分析与解法: 这个问题的解法并不复杂. 遍历A的所有位置 遍历B的所有位置 如果A的位置和B的位置在同一列 输出结果 否则 继续寻找 地图可以用0-8表示A或B可能的9个位置 0

《编程之美》读书笔记(四): 卖书折扣问题的贪心解法

  <编程之美>读书笔记(四):卖书折扣问题的贪心解法        每次看完<编程之美>中的问题,想要亲自演算一下或深入思考的时候,都觉得时间过得很快,动辄一两个小时,如果再把代码敲一遍的话,需要的时间可能更长,真是搞不懂通过微软面试的那些家伙的脑袋到底什么构造,书的序言中提到他们每次面试45分钟,还要写出程序?!在我看来,如果是控制CPU曲线或是中国象棋问题或许还有可能,如果是买书折扣问题,我觉得真的是不太容易,尤其是如果当面试者钻进本题的贪心解法而不是动态规划算法的思路之后,

《多核编程的艺术》读书笔记

感谢网友 郑思遥 投递本稿. 这份笔记是我2013年下半年以来读"The Art of Multiprocessor Programming"这本书的读书笔记.目前有关共享内存并发同步相关的书籍并不多,但是学术文献却不少,跨越的时间范围也非常长,说明人们一直在做出努力. 这本书是这个领域的好书,作为一本好书,它总结了这个领域自发展以来的大量重要成果,介绍了共享内存同步的基本理论,并介绍了大量并发算法和数据结构(主要是无锁算法),包括并发队列.栈.链表.计数器.排序网络.散列.跳表.优先

openmp-我想用MPI+OpenMP混合编程模型实现中国象棋博弈树并行搜索可以吗?怎么实现呢?

问题描述 我想用MPI+OpenMP混合编程模型实现中国象棋博弈树并行搜索可以吗?怎么实现呢? 对于中国象棋的博弈树怎样用MPI+OpenMP混合编程模型分解,MPI是进程级的,OpenMP是线程级的,两者混合可以实现博弈树的并行搜索吗 解决方案 应该是取决与你怎么实现的吧. 解决方案二: 可以混合,关键你怎么组织,调用

编程-用MFC编写中国象棋棋盘

问题描述 用MFC编写中国象棋棋盘 VS2010自动生成几个基础代码 怎样进行改写绘制象棋棋盘和棋子 解决方案 http://blog.csdn.net/vagrxie/article/details/1831995 解决方案二: 想要效果好,还是直接帖图比较可靠! 但你需要 UI 设计人员支持你的工作. 解决方案三: http://download.csdn.net/download/a435886030/7014285