本文适合对数据结构与算法有一定基础的进行阅读。
基于《数据结构与算法:python语言实现》的概念与算法的简要总结。
数据结构是组织和访问数据的一种系统化方式,算法是在有限的时间里一步步执行某些任务的程序。
在算法分析中,重点研究的是运行时间的增长率,采用宏观方法把运行时间视为输入大小为 n n n 的函数。
用来表示算法时间复杂度的 O ( g ( n ) ) O(g(n)) O(g(n)) 的含义为算法的时间复杂度小于或等于 c g ( n ) cg(n) cg(n)。
Ω ( g ( n ) ) \Omega(g(n)) Ω(g(n))与 O ( g ( n ) ) 相 反 O(g(n))相反 O(g(n))相反,其表示的含义为算法的时间复杂度大于或等于 c g ( n ) cg(n) cg(n)。
反证法,归纳法,循环不变量
通过使用逆否命题与矛盾证明。
德摩根定律:“p或者q”的否定形式为"非p并非q";“p并q”的否定形式为“非p或者非q”。
通过建立递推假说,实现对问题的归纳证明。
一定有一种可以退出程序的情况;
总是在尝试将一个问题化简到更小的规模
父问题与子问题不能有重叠的部分
1.明确函数的功能
2.递归结束条件
3.递推关系式
当一个函数执行两个递归调用时,则称其使用了二路递归,使用多于二次递归调用的即为多重递归。
尽量避免低效率的递归,因为其容易产生巨大的递归层数,影响到算法的执行效率。
其次,二路递归与多重递归随着层数的加深将产生总数巨大的函数调用,容易迅速耗尽计算机资源。
能够简洁地利用重复结构呈现诸多问题,通过使算法描述以递归的方式利用重复结构,经常可以避开复杂的案例分析与嵌套循环,使得算法描述的可读性更强。
但是由于需要跟踪每个嵌套调用的状态的活动记录,因此当计算机内存成本高时,从递归算法得到非递归算法是有用的。通常可以使用堆栈结构使递归算法转换为非递归算法。
包括列表、元组、字符串,共性是支持下标访问。
连续数组在内存中占用连续的内存空间(当数组中每个元素占用字节数不同时,需要按最大元素占用字节数分配空间,造成空间浪费)。
引用数组在一个数组中存储对象的引用(这样数组中每个元素占用字节数都是相同的,但是由于存储了对象的引用,也导致了占用内存增多)。
紧凑数组在内存上连续存储,原始数据按位存放。
根据添加数组元素需要,增加数组分配的内存的数目,通常预分配与当前数组元素两倍的内存,通过摊销可以达到近似O(1)的效率。
**优点:**可以通过索引在O(1)时间内获取数组元素。
缺点:
1.一个动态数组的长度可能超过其实际存储的数组元素所需的长度
2.在实时系统中对操作的摊销边界是不可接受的
3.在一个数组内部执行插入与删除操作的代价太高
双向链表维护prev与next两个引用。
为了避免涉及双向链表的边界问题,通常设置头哨兵与尾哨兵。可以简化操作逻辑。
有利于实行垃圾回收,释放占用内存。
由于访问的局部性原理,使用启发式算法,利用访问序列的局部性,每次访问一个元素即将它移动到列表的最前端。
1.数组提供复杂度为O(1)的基于整数索引的访问一个元素的方法
2.通常,具有等效边界的操作使用基于数据结构运行一个常数因子比基于链表的结构运行更有效率。
例如:队列的enqueue操作,忽略调整数组大小的问题。列表需要的操作为:一个新索引的计算,一个整数的增量,数组中存储一个元素的引用。链表需要:节点的实例化,节点的合适链接和整数增量。链表版本中CPU操作的实际数量更多,特别是考虑到新节点的实例化。
3.相较于链式结构,基于数组的表示使用存储的比例更少
1.基于链表的结构为它们的操作提供最坏情况的时间界限。这与动态数组的扩张与收缩相关联的摊销边界相对应。
2.基于链表的结构支持在任意位置进行时间复杂度为O(1)的插入与删除。
非线性数据结构——树,树是存储一系列元素的有限节点的集合。
最顶部的元素为树根。
一个没有孩子的节点 v 称为外部节点(叶子节点),一个有一个或者多个孩子的节点 v 称为内部节点。
树T的一条边指的是一对节点(v,u),u 是 v 的父节点或者 v 是 u 的父节点。路径是指一系列的节点。
假设p是树的一个节点,那么p的深度就是节点p的祖先的个数,不包括p。
若每个节点都有零个或者两个子节点,则这样的二叉树为完全二叉树(满二叉树)。
中序遍历的主要应用的把有序序列元素存储在二叉树中,定义这种树为二叉搜索树。
每次从优先级队列弹出时遍历一遍,选择最小值弹出实现排序,效率为O(n^2)。
归并排序与快速排序都使用了分治法的设计模式。
由于快速排序如果每次取最后一个元素作为基准值的话,当一个序列是已经排序好的,那么快速排序的效率则是O(n^2),因此可以使用随机快速排序,每次随机从序列中取值作为基准值。
建立一个数组,数组中每个元素是一个序列,将需要排序的序列的元素值作为数组的索引,将元素值插入该索引指向的序列的末端。
该方法不依赖与比较,依赖于键值,因此只适用于下面描述的情况:
当不同的关键字出现相同的哈希码时,会产生冲突。
处理冲突有两种方法:
使用线性探测将影响到获取元素、设置元素、删除元素几中操作。因为其是向后找到空桶插入的冲突元素。
例如:寻找元素时,找到一个索引如果键值不匹配,并向后知道找到空桶才停止。
例如:删除元素37,将导致15不能被找到(26之后有空桶)。
下面文字中 n 代表元组的数目(映射),N代表桶数组的大小。
通过使用搜索树可以时线性时间的查找达到对数时间查找的性能。
二叉树高度为 log(n)
由于某些情况下树的高度与n成比例的不平衡树,因此需要保证树的高度与n成对数关系,以保证算法的性能。
通过旋转与重组可以实现树的平衡。
伸展树是向前启发式搜索树的一种变形,伸展树在伸展时仍然保持着有序树的特征。
其在最差情况下的效率为O(n)但是其摊销性能较好为O(logn)。
(2,4)搜索树是多路搜索树的一种特殊例子。
(2,4)搜索树的属性:
由于(2,4)树的子节点中有一个前节点 c 0 = − ∞ c_0=- \infty c0=−∞与一个后节点 c d = + ∞ c_d = +\infty cd=+∞,如果一个内部节点是d-node则其有d-1个子节点有值,分别为 c 1 , . . . c d − 1 c_1,...c_d-1 c1,...cd−1,其中 d 最大为4。
(2,4)树的性能与AVL树一样。
通过分裂与融合操作保持平衡。
红黑树的渐进性能与AVL树的渐进性能相同。
红黑树的优点是在于插入或删除只需要常数步的调整操作。AVL树与(2,4)树在最坏情况下需要对数倍的时间操作。
假设进行模式匹配,模式字符串长度为 m m m,文本字符串长度为 n n n,则时间复杂度为 O ( n m ) O(nm) O(nm)
首先,镜像启发式算法只要匹配最后一个模式字符,如果不匹配,那么前边的字符就不必再进行比较了,增加了效率。
例如:只需比较S与E,前面六个字符即不必比较了。
那么与此相结合的字幕跳跃式启发式算法,分两种情况,
第一种情况是:如果不匹配的字符不在模式字符串内,则直接将模式字符串跳跃到不匹配字符之后。
例如:从上图到这里
第二种情况是:将模式字符串中移动到包含该不匹配字符的位置。
例如:
其中查找模式字符串中是否包含不匹配字符以及所在位置可以通过简历一个查阅表以提高效率,达到 O ( 1 ) O(1) O(1)的查找效率。
该算法中还结合了KMP算法的思想,这部分放在KMP算法中介绍。
KMP算法主要是通过一个失败函数(部分匹配值表)来实现提高效率的。
这个部分匹配值只需要根据模式字符串计算即可得到。
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- “ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A”,长度为1;
- “ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB”,长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
由此即可计算得到部分匹配值表。
KMP算法的执行分为两种情况,第一种情况是字符不匹配,并且没有已匹配字符:
例如:
不匹配,后移一位
不匹配,后移一位
第二种情况是:已有匹配字符,但是模式字符串未完全匹配
例如:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
此时移动位数(4)=已匹配字符数(6)-对应部分匹配值(2)
得到:
此时移动位数(2)=已匹配字符数(2)-对应部分匹配值(0)
森林是没有循环的图,树是连通的森林,即没有循环的连通图。
当存在一个以集合 v 1 , . . . , v n {v_1,...,v_n} v1,...,vn中的点为中间顶点的有向路径的,其首尾端点的边在原图 G → \overrightarrow{G} G的传递闭包 G n → \overrightarrow{G_n} Gn中。
没有有向循环的图为有向非循环图。
利用了贪心设计模式,从一个顶点向它的邻域扩散,逐次更新顶点的最小路径值。
未完待续
Python不支持正式的访问控制,但以一个下划线开头的名字都看作受保护的,而以双下划线开头的名字(除了特殊的方法)是被看作私有的。虽然是看作被保护和私有的,但是也可以访问,只是不建议访问。
http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html
https://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
https://book.douban.com/subject/30323938/