提醒:本页面将不再更新、维护或者支持,文章、评论所叙述内容存在时效性,涉及技术细节或者软件使用方面不保证能够完全有效可操作,请谨慎参考!

原文发表于2009年3月7日

今天解皞同学询问了一道编程题,要求是用表驱动法也就是查表的办法解决,题目(部分)如下:

映射关系如下:
A, B, 和C 映射到 2
D, E, 和F 映射到 3
G, H, 和I 映射到 4
J, K, 和L 映射到 5
M, N, 和O 映射到 6
P, R, 和S 映射到 7
T, U, 和V 映射到 8
W, X, 和Y 映射到 9
Q和Z没有映射到任何数字。

可能最近一个链表的项目做晕头了,我的第一反应竟然是链表,后来想了下,这个应该使用普通数组的表结构会比较合理,我给出的建表方案如下:

映射关系是连续的,假设没有映射是1,那么映射关系应该是1-9这九个数字,化为数组下标应该是0-8。然后知道这是个一对多的关系,首先0对Q和Z、1对A、B和C……假设数组就是0~8的。

如何才能将一个数组下标对应的空间包含相应的多个联系呢?

我们可以通过状态位来判断,这里需要用到点位运算的知识。

设二元序列如下:
<0000 0001, A>
<0000 0010, B>
<0000 0100, C>
<0000 1000, D>
<0001 0000, E>
<0010 0000, F>
<0100 0000, G>
(省略若干)

大家应该能看出什么规律了,二进制的一位代表一个状态位,那么我们可以通过这些状态位索引字母A-Z。

那么一个无符号长整型( unsigned long )即可包含全部大写字母表的信息,比如包含A、B和C的信息,只要将A、B和C的二进制状态位按位或即可。

因此可以得到一张编码表: <0x2010000> <Q, Z>
<0x0000007> <A, B, C>
<0x0000038> <D, E, F>
<0x00001c0> <G, H, I>
<0x0000e00> <J, K, L>
<0x0007000> <M, N, O>
<0x0068000> <P, R, S>
<0x0380000> <T, U, V>
<0x1c00000> <W, X, Y>

然后可以通过查这张表,然后用检测某位的方式判断映射关系,检测某位只需要按位与判断结果是否为0即可。

下面给出源代码:

#include <stdio.h> 

int main(void) {

  unsigned long letter_table[9] = 
    {0x2010000, 0x0000007, 0x0000038, 0x00001c0,
     0x0000e00, 0x0007000, 0x0068000, 0x0380000,  0x1c00000};

  int get_letter, i;

  puts("Enter letter [A-Z] ?");
  get_letter = getchar();

  if(get_letter < 0x41 || get_letter > 0x5a) {
    fprintf(stderr, "Error letter must A-Z");
    return 1;              
  }
    
  for(i = 0; i < 9; i++) {
    if((((unsigned long)(0x1 << (get_letter - 0x41))) &
      letter_table[i]) != 0) {
      if(i != 0)
        printf("found it!  @ %c = %d\n", get_letter, i + 1);
      else 
        printf("letter %c without number.\n", get_letter);
      break;              
    }      
  }

  return 0;    
}

以上源代码在Dev-CPP GCC下编译通过。

当然不见得这个就是效率很高的,我只是想通过这篇文章给大家编程时拓宽思路,比如位状态变化就是一个比较好的存储信息的方式。

如果想要查表效率很高,我觉得可以根据用户的具体输入,经过简单数学运算可以得到离结果最近的查表起始点,然后查找。

如果对于位操作比较感兴趣的可以参考我的前面的 《简单介绍编程中位运算的使用》