20 Jul 2020
数据结构和算法
让CPU占用率曲线听你指挥
- 让CPU占用率固定在50%,为一条直线。
- 也为直线,但是占用率的值由命令行参数决定。
- 正弦曲线
解答:
-
简单解法:让busy时间和sleep时间各占50%。如果sleep 1s,需要busy循环多少次呢? 假设运行代码的CPU是P4 2.4Ghz,现代CPU每个时钟周期可以执行两条以上的代码,平均值2:
2 400 000 000 * 2 /5 = 960 000 000(循环/秒)
为了不让波形成一个锯齿状,可以降低两个数量级。
- 上述时间需要根据CPU的不同进行调整,缺少适应性。可以用GetTickCount()可以得到系统启动到现在所经历时间的毫秒值,最多能够统计到49.7天。
DWORD startTime = GetTickCount(); while(true) { DWORD startTime = GetTickCount(); while((GetTickCount() - startTime) <= busyTime) ; Sleep(idleTime); }
- 上面的解法都只考虑了只有这个进程的情况,如果还有其它进程呢,那就需要得到当前的性能数据,
中国象棋将帅问题
用比较巧妙的数学思想。
由于1-9分别是两个数组的位置,所以可以采用1-89的自然数:
while(i--) {
if(i/9%3 == i%9%3)
continue;
printf("A=%d, B=%d/n", i/9+1, i%9+1)
}
数据结构
1. 布隆过滤器
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。
如何选择哈希函数个数和布隆过滤器长度
很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。
另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。
2. Bitmap
3. B+树
B树是平衡多叉树
B树定义如下(其中m为阶数):
- 每个节点最多有m-1个关键字(可以存有的键值对)
- 根节点最少可以只有一个关键字
- 非根节点至少有m/2个关键字
- 每个节点中的关键字都按照从小到大的顺序排序,每个关键字的左子树中的所有关键字都小于它,而右子树的所有关键字都大于它。
- 所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
- 每个节点都存有索引和数据,也就是对应的key和value。
所以根节点的关键字数量范围:1 <= k <= m-1,非根节点:m/2 <= k < m-1
B树插入和删除过程中如果破坏了上述规则,则需要进行相应的分裂和合并。
B+树和B树非常相似:
- 根节点至少一个关键字
- 非根节点:m/2 <= k < m-1
不同点:
- 非叶子节点不存储数据,只存储索引,数据都存在叶子节点。
- 每个叶子节点都存有相邻叶子节点的指针,叶子节点本身依关键字的大小自小而大顺序链接。
- 父节点存有右孩子的第一个元素的索引。
B+树相比B树的优势:
- 单一节点存储的元素更多,使得查询的IO次数更少,所以也就使它更适合作为数据库MYSQL的底层数据结构了。
- 所有的查询都要查找到叶子节点,查询性能是稳定的。
- 所有的叶子节点形成一个有序链表,更加便于查找。
LSM Tree(Log Structured Merge Trees)
LSM的思想,在于对数据的修改增量保持在内存中,达到指定的限制后将这些修改操作批量写入到磁盘中,相比较于写入操作的高性能,读取需要合并内存中最近修改的操作和磁盘中历史的数据,即需要先看是否在内存中,若没有命中,还要访问磁盘文件。原理:把一颗大树拆分成N棵小树,数据先写入内存中,随着小树越来越大,内存的小树会flush到磁盘中。磁盘中的树定期做合并操作,合并成一棵大树,以优化读性能。对应于使用LSM的Leveldb来说,对于一个写操作,先写入到memtable中,当memtable达到一定的限制后,这部分转成immutable memtable(不可写),当immutable memtable达到一定限制,将flush到磁盘中,即sstable.,sstable再进行compaction操作。
原理上,无论是B+-Tree还是LSM-Tree都是针对现代存储器的特点而设计的,前者注意利用了Bulk读写,而后者则是力求减少Seek操作,可以说各有侧重。
位运算
- X & 1 == 1 OR 0 判奇偶
- X & (X-1) 清除低位的1
- X & (-X) 得到最低位的1
Til next time,
at 12:14