Algorihtm

119 阅读6分钟

待分类

KMP

image.png
KMP算法与其他字符串子串查找的典型的区别是它先计算子串本身存在的一些相关信息,这样可以保证每次匹配不用从最开始位置查找,而是从最佳的位置开始匹配,从而提高了查找的效率,保证复杂度在O(n+m)规模。KMP算法的精华部分就在于求模式匹配的next数组,如果对状态机有所了解,就能很好的理解这一思想

数组

  1. 原地删除有序数组中的重复项:
    1. me: 快慢指针
  2. 一般所谓的原地:都涉及到交换,而交换,一般需要2个指针分别指向交换的双方
  3. 有些问题我们需要使用双指针,甚至多指针,但要求空间复杂度O(1),这个时候,我们可以通过:index + k的方式实现其他第二 第三指针

LinkedList

  1. 注意检查下标向左向右的两个越界值
  2. 核心方法:是利用双指针步进到指定下标,也就是根据指定下标拿到节点
// 待实现:getNodeByIndex(index)
  1. 要有一个认识:什么是节点行为,什么是list的行为。

Tree

  1. 节点中的right,left:本质上是一个引用(可理解为类指针)。指向的Node一般为一个引用类型(Object等)
  2. 构造一棵搜索树,则它天然有序(left < mid < right)
  3. 所谓的中序、先序、后续遍历,本质上就是访问某个节点(获取该节点的值,并执行相应的回调-访问者)的时机(顺序不同而已)。
    1. 中序遍历:left -> mid -> right,作用:一般对树进行排序操作
    2. 先序遍历:mid -> left -> right, 作用:打印一个结构化的文档
    3. 后序遍历:left -> right -> mid, 作用:计算一个目录及其下文件所占空间的大小
  4. 旋转操作的目的是为了使树平衡,(降低节点的平衡因子到0 -1 1),
  5. 旋转操作的本质:交换节点(一般是三个节点之间)之间的位置(swap,用一个第三变量来记录其中一个值并进行交换,类似排序中的交换)。也就是构造出一个平衡后的子树,然后把该子树移植给之前的子树的root节点。(我们每次处理 只处理当前发现其子树失衡的节点,每个失衡节点,我们递归处理)


6. Tree的一大优点是:父子节点之间有引用关联,利用这个引用(指针),通过遍历、比较、交换等,可以实现很多数据结构。

  1. Tree的核心操作就是指针的传递(在两个有关联节点之间的移动)以及停止遍历的基线(出口)条件 + 交换。
  2. 二叉堆是左侧子节点数据优先:优先插入等,因为左侧其实在数组中是index小于右侧的,这样的话,也就保证了有序。左侧子节点 在最小堆中始终小于右侧子节点。

tree的非递归遍历

  1. 利用栈模拟递归调用:
    1. 遇到先不访问的节点,可以将该节点入栈,等到其他遍历结束,再出栈访问
    2. 一个非递归(利用循环)遍历一个树的单元操作:
// 类似于链表利用节点之间的指针进行步进
while (cur != null) {
  stack.push(cur);
  cur = cur.left;
}

BFS

  1. 队列用来记录当前处理节点的子节点(全部入队列)
  2. 处理发生在出队列的时候,出一个处理一个
    1. 处理:将当前节点的子节点全部入队列
    2. 当前节点处理
    3. 当前节点出队列
  3. 思想感悟:我们看到很多辅助数据结构的作用一般都是记录。

GFS

散列表

  1. 散列表借助于数组实现O(1)的时间复杂度
  2. hash函数 是从一个数据生成对应数组的下标,该下标就是hash函数生成的,在该下标的位置存储的就是该数据。
  3. 这就是一种映射。
  4. 核心:
    1. 散列函数设计
    2. 散列冲突解决

散列函数设计

  1. 设计准则:
    1. 不要太复杂,复杂计算量大,影响性能
    2. 生成的值要随机并且尽可能均匀分布
  2. 设计方法
    1. 数据分析法(例如取编号的后几位作为下标)
    2. 字符的ascll码求和并和散列表的大小取余,取模作为散列值
    3. ...

散列冲突

1. 开放寻址法
  1. 二次探测:二次探测的步长为原来的2次方: hash(key)+0, hash(key)+1^2, hssh(key)+2^2
  2. 双重散列: 准备一组hash函数: hash1(key) hash2(key) hash3(key), 当第一个找到的地址有冲突时,使用第二个hash函数
  3. 装载因子 = 填入表的元素个数 / 散列表的长度,越大 则反映空闲位置越少 冲突越多 散列表性能下降
2. 链表法
  1. 散列表的数组的每一个项:可以看做一个桶或者槽
  2. 每个槽对应的链表长度:k = n/m (n表示散列中数据的个数, m表示槽的个数, 也就是尽可能每个槽对应的链表长度接近)

散列表碰撞

  1. 原理:通过静心构造的数据 使得所有的数据经过hash函数后 散列到同一个槽里没如果基于链表法解决冲突,则散列表就会退化为链表,O(1) 退化为 O(n)

装载因子过大的处理

  1. 动态扩容

二分查找

  1. 下标:mid = low + (high - low)/ 2
  2. 实现:
    1. 循环
    2. 递归
  3. 二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找将不再适用。那针对动态数据集合,如何在其中快速查找某个数据呢?别急,等到二叉树那一节我会详细讲。
  4. 关注点:
    1. 循环退出条件
    2. mid取值
    3. low high的更新
  5. 变形:

递归

  1. 适用场景:适用于多层嵌套但数据结构较为固定的场景。

用栈实现递归问题

动态规划

  1. 参考:blog.csdn.net/u013309870/…
  2. 动态规划的核心:记住已解决的子问题的解。
  3. 记住子问题的解:
    1. 自上向下的备忘录法
    2. 自底向上的动态规划
  4. 动态规划原理:
    1. 最优子结构
      1. 子问题的最优解构造原问题的最优解
    2. 重叠子问题
      1. 递归算法反复求解相同的子问题
      2. 使用数组保存子问题的解
  5. 经典模型:
    1. 线性模型
      1. 状态的排布呈线性
      2. 例子:小朋友一个手电筒过桥的最短时间
    2. 区间模型
    3. 背包模型

背包问题

  1. 组合优化问题
  2. 构造矩阵,利用矩阵可以找到上一层最大的价值(类似于reduce的累加)
  3. 因为每一层的weight都不会超过该层的weight,所以上一层的同一列也不会超过那一层的weight
  4. 最优解:求出记忆值和新值之间的最大值。
  5. 思路模板:构造KS矩阵 -> 分析矩阵(矩阵右下角一般为最优解的值,通过分析矩阵还可以给出最优组合方案)
  6. 内层循环使用weight,也就是算出该重量下可以的最大价值

参考

  • 《学习javascript数据结构与算法》第二版