数据结构与算法之美读书笔记
时间复杂度分析
- 只关注执行次数最多的一段代码
- 加法法则:总复杂度等于量级最大的那段代码的复杂度
- 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
最好、最坏、平均时间复杂度
数组
内存中一块连续的存储空间,有效使用 CPU 的缓存机制,可以很方便的定位元素
- 在 O(1) 的时间通过下标访问到元素
- 插入和删除操作比较低效,平均时间复杂度为 O(n)
- 大小是固定的
Hash 表
底层可以使用数组存储数据,借助 hash 函数找数据对应的下标
Hash 函数的其他用途
- 对隐私数据安全加密
- 唯一标识 和 数据校验(负载均衡)
- 通过一致性 Hash 算法做分布式存储
- 数据分片(对数据取 hash,把类似的数据同步到一台机器上)
链表
- 增加、删除元素较方便,不是连续存储的数据
- 单向链表、双向链表、循环链表(解决约瑟夫问题)、双向循环链表
栈 or 队列
栈是一种操作受限的数据结构,只支持入栈和出栈操作。后进先出是它最大的特点。(特定的数据结构是对特定场景的抽象)
树型结构
- 二叉树
- 二叉查找树(左子树<根节点<右子树)
- 平衡二叉查找树(任意一个节点的左右子树高度相差不能大于 1)
- 红黑树:近似平衡的二叉查找树,解决了数据更新删除引起的维护成本
M 阶 B 树:结点最多有 M 个儿子。(概括来说是一个节点可以拥有多于2个子节点的二叉查找树)
要求:
- 树的根或者是一个树叶儿子数在 2 和 M 之间。
- 非叶子节点的儿子数在 M/2 到 M 之间。
- 所有的树叶在相同的深度。
- 所有元素都保存在叶子节点上。
B+树:(为了支持按照区间查找 和 减少去磁盘取数据)
- 有 k 个子结点的结点必然有k个关键码(非叶子结点的子树指针与关键字个数相同)。
- 非叶结点仅具有索引作用,只包含导航信息,不包含实际的值
- 所有的叶子结点和相连的节点使用双向链表相连,便于区间查找和遍历
树的遍历方式:根据根节点的遍历时间分为前中后序遍历
堆型结构
- 堆是一个完全二叉树
- 堆中的每个节点的值必须大于或者等于每个字节点(大顶堆)
解决问题
- Top K 问题
- 优先级队列
排序
三个基本属性:时间复杂度、空间复杂度、排序算法的稳定性
排序算法的稳定性(排序后相等元素之间原有的先后顺序不变):稳定的排序算法,排序效果可以叠加。(例如按照时间+金额排序)(可以先按照时间排序、再按照金额执行排序)
冒泡排序:只循环比较相邻的数据,最大的数因为比较会下沉,较小的数会逐渐向上冒
插入排序:取未排序区间的元素,在已排序区间找合适的插入位置进行插入
选择排序:和插入排序的思想类似,不同点在于在没有排序的数组元素中进行交换找到最大或最小元素进行排序
查找
二分查找
- 循环退出条件:low<=high
- mid 取值:(low+high)/2 因为数据可能比较大会产生溢出 》low+(high- low)/2 〉low+((high-low)>>1) 将除法转化为位运算提高性能
- low 和 high 需要进行 +- 1更新
字符串匹配算法
KMP 算法(K(m+n))参考链接
- 匹配失败看最后一个匹配数值的 next 是什么
- next 代表可以“跳过匹配”的字符个数
暴力匹配算法(k(nm))
四种常见算法
分治算法:将大的问题拆分成小问题,从子问题中得到原问题的解
回溯算法:遍历所有可选择元素或者数据,如果当前选择不符合问题要求就会产生回溯,即抛弃当前的选择回到上一个状态并进行其他的选择(类似于穷举的解决方式)
穷举算法:枚举法、暴力法,通过搜索所有的解空间得到问题的解
贪心算法:分阶段的工作,在每个阶段做出当前最好的选择,从而希望得到结果是最好或最优的算法
动态规划算法:需要满足(最优子结构、无后效性、重复子问题)
- 最优子结构:问题的最优解包含子问题的最优解
- 无后效性:某阶段状态一旦确定,不受之后阶段的决策影响
- 重读子问题