(转载)

表驱动法

前注:希望我的读书笔记能带你快速翻过20页的书,欢迎讨论http://www.cnblogs.com/jerry19880126

这里谈谈一些学习方法吧,看了二十多年的书的,发现不同的书,有不同的看法:小说类的读起来最轻松,只要跟着作者走就行了,会写书的作者应该能呈现 一些剧情的细节,读者脑海中会形成相应的影像;散文类的读起来最值得细细品味,比如读者里面的散文,不是很长,但读起来会有一种小资情调;技术类的读起来 最吃力了,但这也是自己谋生的必经之路,所以再觉得难,也要啃下去,但技术这个东西,只要肯下功夫,学通之后,就会有一种难以名状的成就感,这种快乐得到 的越多,你的成长就越大。

千万不要像读小说或读散文一样看技术书,技术书是需要思考的,更重要的是需要实践,读多了也就发现虽然内容各不相同,但写作的规律总是大差不差的。给定一种技术,你觉得作者会怎样介绍?我觉得分成三步:第一步说“是什么”,第二步说“为什么”,第三步说“怎么做”。就以本章“表驱动法”为例,《代码大全》作者先是回答了“什么是表驱动”

表驱动法是一种编程模式,从表里面查询信息而不使用逻辑语句(如if或switch)

然后举了一个不使用表驱动的反例,说明不这样做会使代码可读性大大下降,这就回答了第二步。至于第三步,作者花了大量篇幅去介绍,你所看到的大部分内容实际上是第三步。

读书笔记也打算用三步去介绍这个表驱动法。第一步是定义,前面已经抄了书上的一句话,已经说的很明白了,就是用查表来代替if语句或switch语 句。第二步是原因,可以举个例子,如果有这样一个函数int getTotalDayInMonth(int month),它输入一个月份,然后返回这个月份的总天数(不考虑润年,二月以28天计),比如输入5,返回的是31,因为5月里共有31天。

一种写法是这样的:

// 获得某一月中的总天数,monthIndex从 1 开始
int getTotalDayInMonth(int month)
{
    int totalDay = 0;
    if(month == 2)
    {
        totalDay = 28;
    }
    else if(month == 4 || month == 6 || month == 9 || month == 11)
    {
        totalDay = 30;
    }
    else
    {
        totalDay = 31;
    }
    return totalDay;
}

在这种写法里使用了逻辑if判断,里面有很多凭空出现的数字,比如2,4,11等,这些称为magic number的数字出现在程序里是很不好的,因为不好修改与扩充,比如说上面的代码适合与地球上的计算,现在要你去改一个火星上的情况(火星公转时长不同 于地球,所以每个月划分的天数也会不同),你可能就要去改这些magic number了,更复杂的,你可能要因为更多的天数可能性(假定火星7月只有17天,而9月有21天),而添加更多的if分支。但如果像下面这样使用一个 表,就使程序简单多了:

const int totalDayTable[12] =
{
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

// 获得某一月中的总天数,monthIndex从 1 开始
int getTotalDayInMonthFromTable(int month)
{
    return totalDayTable[month - 1];
}

怎么样,把用表与不用表的代码对比一下,是不是要简化许多呢?更有意义的是,如果某个天数变了,可以直接修改或扩充totalDayTable就行了,至于函数则分毫不用修改。

好吧,写到这里,应该已经解决了第二步了,就是使用表驱动可以提高源程序的可读性,使之更简洁而且更容易修改与扩充。

下面走到第三步,也是本章中花篇幅最大的一步了——如何去用表驱动来解决问题。

书上说查表方法可以细分为三种:

(1)     直接访问

(2)     索引访问

(3)     阶梯访问

直接访问是最简单的,查表本质其实就是去索引“键”来获得“值”,有点像获得数组值一样,给定下标index,然后matrix[index]就获 得数组在相应下标处的数值,再如前面的return totalDayTable[month - 1]就是直接用month-1来作为键的,而值可以直接通过查表来获得。

现在有个问题,万一“键”是不能直接用的呢?比如我想设计一个幼儿学习动物的软件,若小孩想查找牛的信息,这时屏幕上会打印出牛的特性,而若小孩想 查找狗的信息,则屏幕上会打印出狗的信息。显然,这里的键是动物名,而值是相应的描述。下标必须是整数,但动物名是string,怎么办呢?数据结构中的 hash表当然可以了,它就是计算string的hash值,通过hash值来索引表格的,但在这里我们不打算用hash值,而是由程序员自行设计 string到int的映射,怎么做呢?很简单啊,自己做个菜单呗,让用户只能选择相应的数字,这样“键”就成int了哈。

代码如下:

class Animal
{
public:
    virtual void print() = 0;
};

class Dog: public Animal
{
public:
    void print()
    {
        cout << "This is Dog..." << endl;
    }
};

class Cat: public Animal
{
public:
    void print()
    {
        cout << "This is Cat..." << endl;
    }
};

class Cow: public Animal
{
public:
    void print()
    {
        cout << "This is Cow..." << endl;
    }
};

Animal* animalTable[] = {
    new Dog, new Cat, new Cow
};

int main()
{
    cout << "想知道哪种动物的描述?" << endl;
    cout << "1. 狗" << endl << "2. 猫" << endl << "3. 奶牛" << endl << endl;
    int choiceIndex;
    cout << "我选择:";
    cin >> choiceIndex;
    assert(choiceIndex >= 1 && choiceIndex <= 3);
    animalTable[choiceIndex - 1]->print();
}

运行结果为:

这里用了C++的多态性,根据不同的实体对象能调用相应的print函数,但我更想表达的是表驱动法的应用,请把目光放在表animalTable 上吧,这样的排序,就是将Dog映射成数字1,Cat映射成数字2了。这是人为的映射,但用途更广的是hash映射,不知道的同学去看看数据结构 吧,hash表可以快查找的利器,面试中常常被问到。

第二种表驱动法是索引访问表,它适用于这样的情况,假设你经营一家商店,有100种商品,每种商品都有一个ID号,但很多商品的描述都差不多,所以 只有30条不同的描述,现在的问题是建立商品与商品描述的表,如何建立?还是同上面做法来一一对应吗?那样描述会扩充到100的,会有70个描述是重复 的!如何解决这个问题呢?方法是建立一个100长的索引,然后这些索引指向相应的描述,注意不同的索引可以指向相同的描述,这样就解决了表数据冗余的问题 啦。

第三种表驱动法是阶梯访问表,它适用于数据不是一个固定的值,而是一个范围的问题,比如将百分制成绩转成五级分制(我们用的优、良、中、合格、不合 格,西方用的A、B、C、D和F),假定转换关系是当成绩在90-100区间,判为A,成绩在80-90区间,判为B,成绩在70-80区间,判为C,成 绩在60-70区间,判为D,成绩在60以下,判为F(failure)。现在的问题是,怎么用表格对付这个范围问题?一种笨笨的方法是申请一个100长 的表,然后在这个表中填充相应的等级就行了,但这样太浪费空间了,有没有更好的方法?

在《代码大全》上是用表格记录范围上限的,但其实用下限也是可以的,我就尝试用下限做了下(A级的下限是90,B级的下限是80…):

//阶梯访问表,顺序查找
const char gradeTable[] = {
    ‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘F‘
};

const int downLimit[] = {
    90, 80, 70, 60
};

int main()
{
    int score = 87;
    int gradeLevel = 0;
    while(gradeTable[gradeLevel] != ‘F‘)
    {
        if(score < downLimit[gradeLevel])
        {
            ++ gradeLevel;
        }
        else
        {
            break;
        }
    }
    cout << "等级为 " << gradeTable[gradeLevel] << endl;
    return 0;
}

运行结果如下:

gradeLevel相当于表指针,在程序中就是通过调整这个表指针来使之指向正确的位置的。

程序还有优化的地方,注意到这些下限是有顺序(降序),那可以用二分查找啊,程序如下:

//阶梯访问表,二分查找
const char gradeTable[] = {
    ‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘F‘
};

const int DONWLIMIT_LENGTH = 4;

const int downLimit[] = {
    90, 80, 70, 60
};

int BinarySearch(int score)
{
    int low = 0;
    int high = DONWLIMIT_LENGTH - 1; //downLimit的最大的Index
    while(low <= high)
    {
        int mid = (low + high) / 2;
        if(score < downLimit[mid])
        {
            low = mid + 1;
        }
        else if(score > downLimit[mid])
        {
            high = mid - 1;
        }
        else
        {
            return mid;
        }
    }
    return low;
}

int main()
{
    int score = 87;
    int gradeLevel = BinarySearch(score);
    cout << "等级为 " << gradeTable[gradeLevel] << endl;
    return 0;
}

怎么样,用表驱动法不仅避免了大量的if或switch分支,还应用上了二分查找法,使得查找复杂度由O(N)下降到了O(logN)!

时间: 2024-10-28 07:58:01

(转载)的相关文章

【转载】C++拷贝构造函数(深拷贝,浅拷贝)

对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. #include <iostream>using namespace std;class CExample {private:     int a;public:     CExample(int b)     { a=b;}     void Show ()     {        cout<

门控时钟-理论分析 ---- 转载

转载自:http://www.chipsbank.com/news_detail/newsId=123.html 门控的基本要求: 1. 所需要的沿(对于正沿触发的寄存器是正沿,对于负沿触发的寄存器是负沿)不增加,不减少: 1. 不会产生毛刺: 1. 使用后功耗要能够降低: 1. 最好面积还会减小. 1. 上升沿触发的门控时钟的结构研究:应用与上升沿触发的寄存器的门控. 1. 直接与门结构: 1. 高电平使能Latch + 与门结构: 1. 低电平使能Latch + 与门结构: 1. 波形研究:

浅谈Java中的equals和==(转载)

在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str2 = new String("hello"); 3 4 System.out.println(str1==str2); 5 System.out.println(str1.equals(str2)); 为什么第4行和第5行的输出结果不一样?==和equals方法之间的区别是什么?如果在初学Java的时候这个问题不弄清楚,就

JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结--转载http://www.cnblogs.com/kubixuesheng/p/5202561.html

转载自---http://www.cnblogs.com/kubixuesheng/p/5202561.html 俗话说,自己写的代码,6个月后也是别人的代码--复习!复习!复习!涉及到的知识点总结如下: 堆栈是栈 JVM栈和本地方法栈划分 Java中的堆,栈和c/c++中的堆,栈 数据结构层面的堆,栈 os层面的堆,栈 JVM的堆,栈和os如何对应 为啥方法的调用需要栈 属于月经问题了,正好碰上有人问我这类比较基础的知识,无奈我自觉回答不是有效果,现在深入浅出的总结下: 前一篇文章总结了:JV

GitHub超详细图文攻略 - Git客户端下载安装 GitHub提交修改源码工作流程 Git分支 标签 过滤 Git版本工作流(转载)

最近听同事说他都在使用GitHub,GitHub是程序员的社区,在里面可以学到很多书上学不到的东西,所以最近在准备入手这方面的知识去尝试学习,正好碰到这么详细完整的文章,就转载了,希望对自己和大家有帮助. GitHub操作总结 : 总结看不明白就看下面的详细讲解. GitHub操作流程 : 第一次提交 : 方案一 : 本地创建项目根目录, 然后与远程GitHub关联, 之后的操作一样; -- 初始化Git仓库 :git init ; -- 提交改变到缓存 :git commit -m 'desc

2.EasyUI学习总结(二)——easyloader分析与使用(转载)

本文转载自:http://www.cnblogs.com/haogj/archive/2013/04/22/3036685.html 使用脚本库总要加载一大堆的样式表和脚本文件,在easyui 中,除了可以使用通常的方式加载之外,还提供了使用 easyloader 加载的方式.这个组件主要是为了按需加载组件而诞生.什么情况下使用它呢? 你觉得一次性导入 easyui 的核心 min js 和 css 太大 你只用到 easyui 的其中几个组件 你想使用其中的一个组件,但是你又不知道这个组件依赖

Data guard概念篇一(转载)

本文转载至以下链接,感谢作者分享! http://tech.it168.com/db/2008-02-14/200802141545840_1.shtml 一.Data Guard配置(Data Guard Configurations) Data Guard是一个集合,由一个primary数据库(生产数据库)及一个或多个standby数据库(最多9个)组成.组成Data Guard的数据库通过Oracle Net连接,并且有可能分布于不同地域.只要各库之间可以相互通信,它们的物理位置并没有什么

【转载】GBDT(MART) 迭代决策树入门教程 | 简介

      转载地址:http://blog.csdn.net/w28971023/article/details/8240756        GBDT(Gradient Boosting Decision Tree) 又叫 MART(Multiple Additive Regression Tree),是一种迭代的决策树算法,该算法由多棵决策树组成,所有树的结论累加起来做最终答案.它在被提出之初就和SVM一起被认为是泛化能力(generalization)较强的算法.近些年更因为被用于搜索排

LIB和DLL的区别与使用(转载)

转载自:http://www.cppblog.com/amazon/archive/2009/09/04/95318.html 共有两种库:一种是LIB包含了函数所在的DLL文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的DLL提供,称为动态链接库dynamic link library.一种是LIB包含函数代码本身,在编译时直接将代码加入程序当中,称为静态链接库static link library.共有两种链接方式:动态链接使用动态链接库,允许可执行模块(.dll文件或.e

[转载]HDFS初探之旅

转载自 http://www.cnblogs.com/xia520pi/archive/2012/05/28/2520813.html , 感谢虾皮工作室这一系列精彩的文章. Hadoop集群(第8期)_HDFS初探之旅 1.HDFS简介 HDFS(Hadoop Distributed File System)是Hadoop项目的核心子项目,是分布式计算中数据存储管理的基础,是基于流数据模式访问和处理超大文件的需求而开发的,可以运行于廉价的商用服务器上.它所具有的高容错.高可靠性.高可扩展性.高