前言
《编程珠玑》确实是一本好书,它里面对算法和数据结构的解读,对问题的分析可以很好帮助编程人员转变以往对数据结构和算法的态度发生改变,转向重视;通常会把本书内容总结为:
- 问题定义;
- 算法设计;
- 数据结构选择;
1、数据结构:
有一些题目很明显,例如存电话号码的磁盘文件排序,只给1m内存,只能用归并排序,算法的确定的,这个时候,数据结构的选择,就决定了算法运行的时间和空间顺序,假设电话号码7位,那么如何表示这7位呢?
方法:
1、编码方式:用 4 Byte,32位整数代表一个号码;或者,13 Byte,一个 Byte 一个号码
2、位图方式:用坐标代表一个数字,第一位为1,第二位为2,那么7位共需要多少bit呢?少于1000万位,也就是1000万 bit = 12500 Byte = 12.5KB
可见,第二种方式可以用位图排序法:这样算法的效率会得到质的提升。但方法2并非适用所有,它要求,第一,数字不重复,第二,范围较少,如果13位整数,那么就是10000000000000bit,2500GB,这样位图就划不来了。
另外,第二种还可以拓展一下,用2bit代表一个数字,那么0代表不存在,非0代表存在且还可以代表存在的个数。
2、基本操作:
旋转一个向量 ,如abcdefg,旋转位defgabc,该问题有多种解法,但不同的解法对效率的影响很大,很多问题场景仅仅都是有限的内存空间和时间下解决的。
方法:
1、如果从i点旋转,那么先用临时空间存0~i的元素,然后i之后的元素全部向前移动i位,再把临时数组复制到最后i位;
2、如果没有临时空间,那么可以用方法1,一位一位的旋转,旋转i次;
3、可以用求逆的算法,例如ab,可以计算a'b',再对整个求逆(a'b')',得到ba;
每一种算法对应一个基本操作,如果基本操作得当,那么灵机一动后,方法3对时间,空间的效果都是最高的。
3、查找:
查找,查找之中,最重要的概念是二分查找,很多数据结构其实都应用了二分查找的理念,例如跳表、红黑树、B+树
应用:数据库
一个应用问题要选对数据结构是最重要的事情,我们知道很多时候,程序员需要在时间和空间上面做取舍,选对结构后是时间和空间的开销兼得的重要方式之一;
为什么是B+树?经常会有人对数据库的数据结构提这个疑问,其实答案就是因为B+树能满足以下三点,而这三点正好是SQL语句的常用操作:
- 可以快速根据id定位到某个元素;
- 支持快速查找某个元素所在的某个范围之中的所有元素;
- 可以支持快速查找到某个元素值前或者后的n个元素;
所以问题定义好之后,我们就开始找对应的数据结构了,第一点,我们想到Hash,但明显,Hash不支持2和3,所以我们需要妥协,选择二分查找,而二分查找可选的数据结构:平衡二叉树、跳表、红黑树、B+树;
第三点,很自然的就是想到双向链表,但链表不满足第二点;这个时候,跳表这种数据结构就比较合适了:
【图片来源网络】
然后对跳表加以改进一下,便有了B+树,看下图,为什么一个节点需要多个元素呢?其实原因就是因为磁盘和内存速度的差距,所以最好是一次性从磁盘加载的数据刚好是一个节点,所以节点便存元素最好了,而这个值的大小,就是缓存页的大小16K;
【图片来源网络】
其实还有一个值得注意的,MongoDB用的数据结构就不是B+树;而是B-树,其实很好理解,文档数据库的查找需求通常不一样,他们只需找到值就可以了,很少做范围查找;
应用:9宫格键盘
这个例子大多数90后都熟悉,那一代的程序员估计很多人都遇到这种需求;下图是一个9宫格键盘,其中如果数据某个词,只能按0、1、2、3、4、5、6、7、8、9这十个键盘,例如fan和dan这两个英文或者拼音,都是按相同的键盘数字,这意味着,所有的单词中,他们都有自己的相同按键的小伙伴,需求:如何根据用户的按键输入,快速在上亿个记录中查找到单词名称为fan的记录呢?
这道题,用到的有两种,第一是排序,第二是查找,排序自然是把上亿个记录排序,查找,是用到二分查找;问题的重点是,按照什么排序?
标识:这个词在编程珠玑第一件见,作为把具有相同按键的称之为同位词,而同位词的正序序列是唯一的,所以这这个序列就是同位词的表示;给上亿个记录都贴上标示后,我们就可以按照标识再进行一次排序,于是,一个有序的记录就出现了,假设我们用的是归并排序。后面的查找就不多说了。这个问题,到此,迎刃而解。