1 Chapter 1
减少io,和函数调用次数
针对io考虑调整输入,输出缓冲大小。
针对调用次数,考虑使用内联或者宏,尽量避免递归
2 chapter 4 查找
字符串A中找子串
bruteforce查找,可以先判断第一字符是否匹配,否则直接跳过该字符。
2.1 Boyer-moore查找,启发式查找。
英文:
http://www.stoimen.com/blog/2012/04/17/computer-algorithms-boyer-moore-string-search-and-matching/
这篇更简洁易懂中文:
http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html
中文还可以:
http://www.cnblogs.com/lanxuezaipiao/p/3452579.html
看得比较晕,上面的这个算法描述还算容易理解,基本思想,要想从字符串A中匹配查找字符串patternB.刚开始让A和B左对齐,然后从B的右边开始逐个字符匹配,遇到必要情况下右移B。利用bad-character算法和good-suffix算法决定向右移动的距离。此算法优点:
采用了启发式策略引导,使可以多移动一些距离,减少比较次数。
更巧妙的是,这两个规则的移动位数,只与搜索词有关,与原字符串无关。因此,可以预先计算生成《坏字符规则表》和《好后缀规则表》。使用时,只要查表比较一下就可以了。
所以需要针对捜索词pattern做一些预处理,计算出一些good-suffix和bad-character的移动量。
此算法应用在editor的search,replace功能中,比如GNUgrep采用了此算法。
Patlen: 要查找的pattern的长度
textLen: 源字符串的长度。
BM算法的时间复杂度:
准备时间:O(patlen)
最坏情况:O(textLen)
一般情况:O(textLen/patlen)
而bruteforce算法的时间复杂度:
准备时间:0
一般情况:O(textLen)
最坏情况:O(textLen*patlen)
一般来说,从一个大的text中查找一个较长的pattern,当textcharacter种类比较多时,BM算法较好。BM算法的精髓是尽量跳过较大的偏移。避免不必要的比较。
2.2 字符串匹配的kmp算法
中文资料:
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
这种算法,于BM算法不同,针对要查找的pattern,每次从pattern的左边开始匹配字符。并维护一个表来觉定当遇到不匹配时右移pattern的偏移量。
2.3 多字符串查找
比如要求从文本中查找多个关键字:abc, def 等等。
方法:
通过构造一个状态机,在某状态匹配一个字符就走到某个状态,来实现查找。依次读入输入流里面的字符,在状态机里面运转。
由于关键字存在overlap,所以状态机也提供回溯的支持,比如在某个状态匹配失败,要求能够回到某个已经匹配了少部分内容的不同状态来再再次尝试匹配下面的内容。
基本思想就是构造状态机。
2.4 模糊字符串查找,非完全匹配字符串
Approximate string matching techniques
给定一个字符串T,给定一个要查找的串p,在T中找最接近P的字串。
那么什么交最接近,给了一个套规则定义了kdifference match. 也就是说允许有k个不同点,比如1个位置不同,多或者少一个字符都算一个不同。
然后算出一个数组d[i,j]来表示p1,....pi 和t1....tj之间的最小的difference.这样数组中最后一行中的最小的difference值也就代表了最接近匹配p的字串的最小difference.那么再从这个位置找字串的起始位置。从而在t中找到一个最接近p的字串。
挺牛逼的看样子,没完全看懂,还是用数学建模来解决问题。
2.5 Phonetic comparasion – soundex算法
语音比较。
语音比较模糊,一个发音游客能有几种匹配。
一种方法就是区分元音,辅音字母,比如忽略元音,给相近的辅音字母分配同样的编号。
那么结果就是,多个单词可能对应同样的编码结果。而实际上这几个单词发音是类似的。
那么可以有着样的场景:
输入一个字符串,把它encode,根据encode结果查找数据库,找到和该encode相同的其他单词。列出来,那么这些单词基本上和输入单词相似。
这些算法是涉及到语言学的。
3 排序
3.1 Bubble sort
O(n2)
3.2 Insert sort
O(n2) 但是由于比较插入操作并不需要比较已经排了序的所有元素,所以耗时比bubblesort较少。
在输入接近已经有序状态时,时间复杂度接近线性。
3.3 Shell sort
普通的插入排序,元素移动较慢。尝试提高此缺点:让元素尽量移动较大距离
将输入分成交叉的若干组。每组插入排序。然后再分组,分组的距离变小,再插入排序。再分组,最后分组的距离是1,执行最后的插入排序。
好处是:元素能够一次性移动较大的位置。
O(N1.25)
因为输入被分组打乱,所以:
没有插入排序在输入接近有序时线性的性能。但也没有最坏的表现。
时间复杂度对于输入较为不敏感。
缺点是:不是稳定的。意味着相等的元素可能位置会互换。
3.4 Quick sort
分而治之法。
选一个privot,把小的挪到左边,大的挪到右边,把privot挪到正确的位置。递归快排左边区域和右边区
域。
privot的选择如果只简单选第一各或者最后一个,对于有序的输入,性能下降到和冒泡一个级别。
并且会导致每一各元素对应一层递归,stack消耗巨大。
所以快排有需要提高的地方:
-
privot的选择,可以采用随机数,避免某者模式的输入序列产生最差的性能。
-
经验表明,较小的输入用插入排序比较快一些。
-
当输入量大时,递归造成的stack较深,可以采取选定了privot,把数据按照大小交换位置,分成两组后,对较小一组采取递归,对较大一组采取循环。这样能较少较大一组产生的stack的深度。
正常的性能O(nlgn)
当输入已经有序时,最差性能。可以靠privot的选择来规避。
也不是稳定算法。
3.5 heap sorting
O(nlgn)
因为每次挪出一个元素后,需要从头调整,耗时正比于树高,lgn,有n各元素,所以是nlgn.
构建初始堆时的操作也是nlgn。所以总耗时一样。
最大堆,最小堆
完全2叉树。
从数组中构建最大堆,然后移除第一个元素,也就是当前最大元素(可以和数组最后元素互换),然后从头向下调整堆。然后再次移除...
两个操作:
-
构建初始堆
-
调整堆
构建初始堆方法,可认为所有叶子节点都是构建完成的堆。从i= (n-1)/2节点开始,也就是从第一个非叶子节点开始。i--。调用调整堆操作。
调整堆的操作可以看成是当堆顶元素是一个新元素时,把他向下”沉”的过程。和孩子比较,有必要的话,和孩子换位,然后再调整孩子堆。
http://blog.csdn.net/morewindows/article/details/6709644/
虽然heapsort也是O(nlgn),但比quicksort大部分时间较慢,但是在普通quicksort出现最坏场景时,heapsort可以在时间上较为稳定。所有操作都是基于树。为什么呢?因为heapsort从一开始就默认建立一个完全2叉树。而不是一个可能会偏的2叉树。
是Unstablesort
3.6 链表上的insertionsort
3.7 Sort on multiple keys – a fix for unstablesort
有多个key的排序-一个针对不稳定排序算法的纠正
quick sort, shell sort, heap sort都是不稳定的。
尝试采用compoundkey, 也就是说修改比较函数。比较两个对象的key1,如果相同,那么比较另一个数值。
例子,transaction列表里,有用户id,date, 如果已经按照date排了序,我们下面用quicksort按照用户id排序,希望拍完后,还是保持了date的正确顺序,就需要排序的比较函数把date也考虑进去,否则date就会乱掉(unstablesort的特性)
3.8 network sort
给定n个数,可以算出一组比较+swap(如果有必要)操作,只需要执行这些操作,最终结果就是排序的结果。这些操作是固定的。而且有些操作涉及到的数据互不影响,因此可以并行,或者用硬件来实现。那么这种算法就会比以前的算法都要快。
但是缺点:不同的n对应不同的操作序列。并且当n很大时,操作序列也会很多。
比较适合n不是很大,且n每次都固定,并且需要很快的排序的场景。
Bose-nelson算法就可以根据输入n算出一组比较操作序列。
3.9 排序算法的选择
Shell, quick, heap sort在较少数据上的排序差距不大,较大数据上才能有差别。
Quick,heap的O(nlgn)在较大输入时,才能体现出对于shell的o(n1.25)的优势。
Shell, heapsort基本上没有最差场景,耗时相对稳定。
Quick sort有最差场景,但是可以通过若干调整尽量避免。
总体来说有限选择quicksort.
4 Trees
二叉树BST,了解插入,删除算法。
4.1 red-black tree
Avl tree是平衡的tree.
red-black tree是avltree的一个实现。
靠红,黑节点,来实现平衡。叶子必须黑,不允许两个相邻节点红。
avl要求高度差最大是1,而红黑树是近似平衡,要求高度差最大一倍。虽然查找速度不如avl,但是量级一样,且维护成本低于avl。
红黑树的特性很重要:
-
Every node is either red orblack.
-
The root is black.
-
Every leaf (NIL) is black.
-
If a node is red, then both its children are black.
-
For each node, all simplepaths from the node to descendant leaves contain thesame number of black nodes.
红黑树的资料:
中文:
http://bbs.csdn.net/topics/350253651
http://blog.csdn.net/eric491179912/article/details/6179908
下面文章相关:
http://blog.csdn.net/v_JULY_v/article/details/6109153
http://blog.csdn.net/v_JULY_v/article/details/6284050
红黑树插入算法,基本上先按照和2叉查找树相同的方法找到插入点,然后插入一个红色节点。
然后以此红色节点检查调整。主要思路就是分情况调整,涉及到调整节点颜色,以及旋转操作以维护rb特性。
详细参看introductionto algorithms 3rd 13.3章节讲了rbtree插入节点算法。
13.4节讲删除节点的算法.
要先弄懂12.3中BST的删除算法。然后更容易弄懂RBtree的删除算法。
总体上讲,红黑树插入删除算法过于复杂,主要罗列了若干场景,而且依赖算法把若干场景转化成其他场景,最终解决问题。
其算法记不住。
4.2 splay tree.
也叫伸展树,他不保证树的平衡,但是当每访问一个节点,就通过zig,zag, zig-zig, zag-zag, zig-zag,zag-zig等操作将该节点旋转为根节点。这样能够将频繁被访问的节点挪到靠近树根的位置。从而提供较好的查找效率。所以他在性能上体现的是多次的非随机性查找上的优势。
时间复杂度O(lgn)
可以用来实现cache,garbage collection 算法。
缺点:因为不平衡,所以有可能深度是n.
此外,每次读访问,都会造成结构变化,所以对于多线程访问,控制上较为复杂。
可以提供区间操作,比如找到在[a,b]区间的所有的节点:
可以先找到小于a的节点并挪到树根,然后找到大于b的节点,在右子树中,将其挪到树根。那么此时,右子树的左子树就是[a,b]区间内部的节点。
有一种通过构建splaytree来实现排序的算法,叫做splaysort.
也是O(nlgn)粒度,一般情况下,慢于quicksort, 但是当输入较为有序时,快于其他算法。
伸展树的splay操作有两种实现方式,一种是自底向上,从要做splay操作的节点开始,向上找到其父亲,祖父节点,一次旋转。缺点需要维护向上的指针。
另一种方法是自顶向下,优点:在查找一个节点的过程中,就完成splay操作,当节点找到后,splay操作可以马上完成。
基本方法是:将树分成左,中,右3棵树,开始时,左,右是空树。判断要找节点x,如果比中树根节点大,那么肯定在根树的右子树上,那么中树在根节点左旋,将根节点和根节点的左子树分出来链接到左树上。如果要找节点比根节点小,那么肯定在左子树上,那么就右旋并把根节点和右子树分开链接到右树上。一次向下走。最后三颗树合并。
注意向左树合并新内容时,要将何如的内容链接到左树的最大节点的右分支上。向右树合并新内容时,要将新内容添加到右树的最小节点的左分支上。
具体内容可以看如下:
中文:
http://blog.csdn.net/ljsspace/article/details/6461005
http://blog.csdn.net/leolin_/article/details/6436037
4.3 B-tree
是分支数目>2的平衡树
B-tree适合于处理数据存储在磁盘的情景。
数据库,文件系统
增加一个节点中的存储的key的数据量,从而也增加了child节点的数目。这样减少了查找节点的操作,一次性获得较多内容。
节点的孩子的数目不定,但是至少halffull. 比如最多允许有m各孩子,那么至少有m/2个。这保证了空间的使用率。
http://blog.csdn.net/hbhhww/article/details/8206846
https://en.wikipedia.org/wiki/B-tree
5 data compression数据压缩
5.1 run length encoding, rle 压缩
将重复字符用字符本身和重复的数量表示。
引入一个特殊字符表明encoding的开始。
简单,快速。
5.2 huffman encoding
先扫描一边输入文件,记录所有的字符的出现频率。然后排序,构造一个2叉树,树的叶子是字符,从树根,到叶子的路径决定了叶子上的字符的编码。保证频率高的字符的编码长度短。这样所有的字符都有了编码。然后再过一遍输入,将所有字符编码。
缺点:只是针对字符的编码,无法解决重复出现的字符串pattern的压缩。
于是出现了dictionarybased compression
5.3 sliding window compression
在压缩/解压缩过程中,维护一个固定长度的window,里面存储之前刚出里过得原始内容,当下面要处理的内容有和window中匹配的串时,就可以压缩。这个window一直向前走。所以叫做slidingwindow算法。
5.4 lzw 压缩算法
中文:
http://1454396751.iteye.com/blog/1912156
https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
基本思想:不需保存字典,字典在压缩和解压缩时动态生成。开始时,字典中预先保存所有字符。读取输入字符时,将前一个字符或者字典中的词和当前字符结合成一个新词,看字典中是否有此新词,如果有,继续读取下一字符,如果没有,就将前一个字符或者字典中的词对应的编码输出,并生成一个新词计入字典。生成的新词都对应一个新的编码。
每个输出编码大小要大于原始字符编码。增加的空间为了表示字典中的词。
比如8bit可以表示255个字符,那么我们用12bit就可表示更多的内容,多出来的内容就可来表示一些字典中的词。
6 data integrity and validation
Crc-16
crc-32