Theme NexT works best with JavaScript enabled
记录学习数据结构的总结,希望可以不拘泥于细节和语言,重视应用与推演.
初步涉及KMP时,有些蒙蔽,还好遇到了 阮一峰 的讲解,才啃下来.博文图来自资料链接.
资料来源如下
从头到尾彻底理解KMP(2014年8月22日版) 如何更好的理解和掌握 KMP 算法? - 海纳的回答 - 知乎 字符串匹配的KMP算法 阮一峰 KMP 暴力搜索 搜索字符串时最常用的是暴力搜索,
一个字符一个字符去比较,第一位相同,比较第二位 遇到不同字符,目标字符指针后移一位,重复. 直到遇到模式字符终止符.返回目标字符指针位置. 以下是暴力搜素的C语言一个实现版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int ViolentMatch (char * s, char * p) { int sLen = strlen (s); int pLen = strlen (p); int i = 0 ; int j = 0 ; while (i < sLen && j < pLen) { if (s[i] == p[j]) { i++; j++; } else { i = i - j + 1 ; j = 0 ; } } if (j == pLen) return i - j; else return -1 ; }
对暴力搜索而言,整个流程上改进空间不大,主要集中在当搜索过程.见示例
可以看到整个搜索过程,最明显的一个缺陷是,搜索到模式字符串的中间很长一段了,遇到一个坏字符,目标字符串指针移位1.模式字符还是要从头开始比较,模式字符在上一轮坏字符之前的比较相当于白做了.
KMP算法 当空格与D不匹配时,前面六个字符是”ABCDAB”已知。KMP算法的想法是,设法利用这个已知信息,不要把”搜索位置” j
移回已经比较过的位置,而是继续把它向后移。
经过KMP处理六个字符已知字符串”ABCDAB”后,可以得到一个表格,对应模式字符串每一位可前移的位数.如图
于是整个过程优化如下.
但空格与D失配,其前一位 (B->2),所以 j=2
但空格与C依旧失配.其前一位 (B->0),所以 j=0
对应c代码变化如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int KMP (char * t, char * p) { int sLen = strlen (s); int pLen = strlen (p); int i = 0 ; int j = 0 ; while (i < sLen && j < pLen) { if (j == -1 || s[i] == p[j]) { i++; j++; } else { j = next[j]; } } if (j == pLen) return i - j; else return -1 ; }
部分匹配表(Partial Match Table),next[]数组 这里大量摘录了如何更好的理解和掌握 KMP 算法? - 海纳的回答 - 知乎 的答案.
上文提及的表格即部分匹配表.
字符串的前缀和后缀。
前缀: 如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀。例如,”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。 后缀: 后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀,例如,”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。 要注意的是,字符串本身并不是自己的后缀。 PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度,如图
这里换一组字符串“abababca”
说明next数组求法
求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。
具体来说,就是从模式字符串的第一位(注意,不包括第0位)开始对自身进行字符串搜索(KMP)。 在任一位置,能匹配的最长长度(j值
)就是当前位置的next值。如下图所示。
如图
c语言实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void GetNext (char * p,int next[]) { int pLen = strlen (p); next[0 ] = -1 ; int k = -1 ; int j = 0 ; while (j < pLen - 1 ) { if (k == -1 || p[j] == p[k]) { ++k; ++j; next[j] = k; } else { k = next[k]; } } }
流程(部分):
next[6]=4 p[6] != p[4]; k = next[4] = 2 p[6] != p[2]; k = next[2] = 0 p[6] != p[0]; k = next[0] = -1 k == -1; ++k; ++j; next[7]=k=0; …… 回到字符串”ABCDAB”,得到的next数组如下
next[]数组改进 当 j
沿着next[]数组回溯时,可能产生一种情况是:连续几次回溯到的字符相同,即连续几次比较了模式字符串里相同的字符.
如上一节流程部分.
p[4] p[2] p[0]都为元素a,实际只需要与 p[6] 比较一次即可. 未避免这种情况,可以对next[]数组回溯链上,连续相同的字符合并,直接取前一个字符对应位置next[]数组的值.由此产生的数组叫nextval数组
.
c代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void GetNextval (char * p,int next[]) { int pLen = strlen (p); next[0 ] = -1 ; int k = -1 ; int j = 0 ; while (j < pLen - 1 ) { if (k == -1 || p[j] == p[k]) { ++k; ++j; if (p[k]==p[j]) { next[j] = next[k]; } else { next[j] = k; } } else { k = next[k]; } } }
资料来源如下
编程环境
导语
记录编译LineageOS 15.1的过程,备忘. 编程环境
问题 过程 Google到了,类似故障.https://github.com/facebook/react-native/issues/18884
解决 关闭 Android Studio的Advanced Profiling
即可,非代码错误 备注 分析日志 需求 简单分析日志文件中两个时间差. 计算平均值,中位数,方差等. 图形化展示. 分析 用到的功能
系统io,只需要按行读入即可,当然需要指定gbk编码 科学计算的函数库 简单的画图库 python库
系统的 io
库即可 numpy
库,大材小用matplotlib.pyplot
,大材小用示例 按行读入
1 2 3 4 5 6 7 8 9 with open (path,encoding='gbk' ) as f: for number, line in enumerate (f): line = line.strip().replace(' ' ,'' ) try : ln1 = line.index(str1) except ValueError as e: ln1 = 1
得到一个list 统计
1 2 3 4 5 print ("数量" ,len (time))print ("平均值" ,np.mean(time))print ("最大值" ,np.max (time))print ("最小值" ,np.min (time))print ("方差" ,np.var(time))
画图
1 2 3 4 5 6 7 for ts in x : tx.append(datetime.datetime.fromtimestamp(ts/1000 )) plt.plot(tx,time) plt.title('len%d, mean%d, max%d, min%d' % (len (time),np.mean(time),np.max (time),np.min (time))) plt.show()
记录学习数据结构的总结,希望可以不拘泥于细节和语言,重视应用与推演. 资料来源如下
选择排序 步骤
寻找数组内最小的元素 与未排列的第一个元素交换位置. 重复1 2 知道数组排列完成. 代码
内循环 比较当前元素与已知最小元素 外循环 交换元素位置 性质
运行时间与输入无关..数组有序无序不影响时间. 数据移动最少.交换次数 与 数组长度 为线性关系. 长度为N的数组 , 选择排序 N2/2 次比较,及N次交换. 分析
最直观的排序算法. 无法利用数组的初始状态进行优化.(后续重点) 冒泡排序 步骤
遍历未排列数组,比较两个相邻元素,依照规则 交换/不交换位置. 重复1,直到完成数组排序. 代码
内循环 冒泡整个未排序数组 外循环 控制内循环 冒泡范围 分析
插入排序 步骤
从第一个元素开始,该元素可以认为已经被排序 取出下一个元素,比较已排序序列,找到新元素位置 将新元素插入到该位置后 重复步骤直到排序完成. 代码
取出新元素 内循环查找新元素位置 插入新元素 外循环控制 性质
随机数组N, 平均需要 ~ N2/4 次比较及 ~N2/4 次交换. 最坏 N2/2 次比较 N2/2 次交换 最好 N-1 次比较 0 次交换(已经排序好的….😂) 分析
考虑了数组的初始状态,对于大多数元素有序的数组,插入排序可能是最快的. 但对于随机数组 还不够. 优化
查找新元素位置: 二分法查找.而不是遍历. 插入新元素时,算法第四版 建议,内循环改为类似冒泡方式,将大元素右移 希尔排序 对插入排序分析时,有这样的结论: 对于大多数元素有序的数组,插入排序时间最快,时间为线性级别. 将任意输入的数组 大多数元素有序,然后最后使用 插入排序.
插入排序对于 随机数组的瓶颈 在于,一个元素到达正确位置,一次只能移动一位.那么一次可以移动多位,交换至很远位置,可以改善插入排序的性能.
思路: 将数组等间隔 h
分裂,进行插入排序.拆分后的子数组,插入排序成本较低,进行插入排序.
完成后,原数组的有序性增加 插入排序成本降低,之后再进行颗粒度更细的拆分-排序.一级一级的降低 插入排序成本.直到最后进行 h=1
的插入排序.
步骤
对数组进行间隔 h 的插入排序. 依照递增序列,取 h 值,直到 h = 1,为止,排序完成. 代码
内循环为 间隔 h 的插入排序 外循环为 计算 h 值的递增序列. 性质
希尔排序的性质并不容易概括,实际上没有完整概括. 目前可以证明的:希尔排序的运行时间 达不到 N2 级别. 分析
考虑了数组初始状态.是如今提到的排序算法中,第一个可以适用与大容量数组排序的. h 递增序列的选择,会很大程度上影响希尔排序效果.实际工程中适用那种,需要自行选择. 嵌入式系统中没有快排等库函数可选时,首选希尔排序.之后再考虑其他排序算法. 归并排序 快速排序 应用最为广泛. 原地排序 时间成本 NlogN
但是! 坑不少…
原理
对于数组中 某个元素 都进行切分操作,大于/小于 该元素的所有元素 都在 一边. 将数组切分为2个子数组. 递归,直到切分子数组元素个数为1,完成排序. 与归并排序 在数组分解上 一致. 步骤
取出数组中某个元素 作为切分元素. 对数组执行切分. 对子数组执行 1 2 直到子数组元素个数为1.递归. 坑
原地切分,创建大量新对象性能上不可接受 保证访问不越界. 慎重处理最大最小元素. 相同元素谨慎处理. 性能改进
切分元素对性能影响及其重要.最糟糕情况: 大部分元素有序,使用极值去切分. 为避免这种情况,快排之前,将输入数组乱序处理. 对于大型数组 取切分元素时 考虑去3 选 1. 小数组 重复元素一分为二,对于存在大量重复元素的数组,无效的比较次数较多. 改为 三取样切分 三切分取样 切分时,不再一分为二,改为 < = >.三部分 三项切分的信息量最优. 对于存在大量重复元素的数组,三切分取样,直接将时间 由 NlogN 降低到了 N . 记录学习数据结构的总结,希望可以不拘泥于细节和语言,重视应用与推演. 资料来源如下
查找 基于符号表的抽象来组织查找有关内容.分为 key-value ,有时又称字典. 分别介绍 二叉查找树 红黑树 及 散列表 3种经典的实现. 符号表 其他 有序符号表
1 2 3 4 main() max() floor(Key key) select(int k)
无序符号表
二叉查找树BST 数据结构:结点
一个唯一key 对应value 一条左链接 一条右链接 一个节点计数器(该节点的子节点总数) get
方法 表中存在对应方法即返回对应值,否则返回空. 代码:递归实现
实现get(Node x,Key key)
查找Key 对应值,没有返回null 对比 x 与 key 值. 根据结果返回 get(x.right,key)
or get(x.left,key)
get由传入根节点调用 get(Node x,Key key)
put
方法 存在则更新值,不存在则插入新值. 代码:递归实现
实现Node put(Node x,Key key,Value val)
x为空,则创建新节点并插入key val 对比x key 值 x = key,则更新值. 根据结果返回 put(x.right,key,val)
or put(x.left,key,val)
更新节点计数器 put传入根节点 调用Node put(Node x,Key key,Value val)
分析
如同union-find
问题,二叉树生长的方式及平均深度对查找及插入的影响很大.二叉查找树性能非常依赖于 key分布足够随机. 有序符号表 最大最小
传入节点左链接是否为空? 返回传入节点 : 递归调用min(x.left) 传入节点左链接是否为空? 返回传入节点 : 递归调用mam(x.right) 向上/下取整
递归传入节点左链接 为空.返回 null 传入节点左链接 = key-value 返回 传入节点 传入节点左链接 < key-value 返回 递归传入左链接 传入节点左链接 > key-value 递归传入右链接 递归调用. 排名
与节点计数器有关. key = 传入节点,返回左子树节点总数. key < 传入节点,返回 递归左子树. key > 传入节点,返回 递归右子树. 删除最大/最小键
删除 -二叉树中间呢?
查找到要删除的节点 保存 父链接 左右子链接. 选择右子树,查找最小节点.(不断选择左子树,直到遇到左子链接为空元素) 将 右子树最小元素 移植到 已删除元素位置. 处理 右子树最小元素 右子链接(直接链接到父节点) 可能存在性能问题. 范围查找
首先遍历操作递归要不要简单 x为空? 返回 过程处理. 不为空, 递归左节点 递归右节点. 在过程处理中进行过滤操作. 因二叉树数值相对有序,舍弃掉不存在想要值的子树. 平衡查找树 只使用二叉树而保持树生长的平衡,代价很高. so,对节点的基础数据结构动一些手脚,引入 2-3 树. 最大的变化在于插入新元素.但随之带来的完美平衡的 2-3 树,让数据结构复杂度很值. 节点可以保存2个key,3个子链接.代表 <a ,a< <b ,>b 3种.
随之带来的好处,树可以一直保持为 完美平衡.所有空链接,到根节点的距离相同.
查找
相对二叉树变动不大.依旧是递归实现. 在判断时加入了2 3节点的指向判断,难度不大,直到遇到空链接为止. 插入
红黑树 将2-3树中 1个 3节点 拆分为 2个2节点,中间使用红色链接.
插入/删除 相较于2-3树,复杂.但基础数据结构简单.
牺牲一些平衡性,换来 程序设计的便利.
前置要求
节点是红色或黑色。 根是黑色。 所有叶子都是黑色(叶子是NIL节点)。 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。) 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 数据结构
方法实现 基础操作 右/左旋
颜色变化
查找
插入
类比2-3树,更加复杂,但基本过程一致. 查找位置->插入节点->修复红黑树. 插入 散列表 记录学习数据结构的总结,希望可以不拘泥于细节和语言,重视应用与推演. 资料来源如下
记录学习数据结构的总结,希望可以不拘泥于细节和语言,重视应用与推演. 资料来源如下
问题描述 白话文: 一堆网点,随机相连,设计一个算法快速检查两个点是否联通.
物理对应: 闲的一堆金属触点,随机用导线连接,快速查找两个点电路通不通.
精确描述:
定义如下API:
1 2 3 4 5 UF(int N) ; void union (int p,int q) int find (int p) boolean conneted (int p ,int q) int count ()
设计算法的任务分解为
实现算法 quick-find quick-union 肯定要对标识量下手了,极端一点,将标识量 改为类似树木生长的实现,每个连通拥有一个根元素. conneted 判断两个节点的根元素是否相同. union 连通 即将两个连通的根元素 链接. find 直接返回根节点.
这样 union 的复杂度降低,找到根节点,新建一个链接即可完成.
分析
逻辑上也比较好理解,类似 连通节点,用绳子串起来了. union 的时间复杂度 降低到了线性级别. 但 find 的成本在最糟糕情况下到了 平方 级别. 最糟糕生长情况,从上到下,一个深度只有一个节点. 改进
由导致 find 最糟糕情况入手,避免这种情况.关注点在 树生长的过程. 加权quick-union 我们的目标 控制树的生长,即控制树生长的深度,尽力减少平均深度.
主要做法: union 时,区分大小树,(节点多的树为大),小树只能链接到大树.代码上 要增加一个 数组来标识 一个连通的节点数.
分析
可以很好控制 树生长的高度. 各个方法的时间复杂度,平衡很好. 但是还不是最完美. 改进
路径压缩的加权quick-union 对 union 操作似乎无法继续改进.
但是从直觉上考虑,要控制树的深度,最直接的情况就是所有节点直接连接到根节点.
so,在 find 操作中 加入这个转换,将所有节点 直接 连接到根节点.
分析
这是我们目前可以找到的最优解. 但不是所有操作都可以在场数级别. 总结 完整详细定义问题,找到解决问题所必须的抽象操作并定义 API
对于搜索类操作,改进算法的一个方面是: 增加一个数据点可以访问的维度.不限于周围元素,通过某种对应关系,拓展维度.