基础部分
时间复杂度
- 作用:指出(最糟糕/平均情况下)算法运行时操作数的增数
- 时间复杂度表示的是运算次数。实际单次的执行时间可能不一致。(牺牲单次执行时间,减少次数;达到减少总时间的目的)
名称 | 表示法 O(x) |
---|---|
常量时间 | 1 |
对数时间 | log n |
线性时间(linear time) | n |
n * log n | |
平方时间 | n^2 |
阶乘时间 | n! |
PS: log 表示 log2 —— 每次过滤一半(1/2),经过几次可以只留下一个
图
- 定义:模拟一组连接;如:x 与 y 的关系
- 组成
- 节点 node —— x/y
- 边 edge —— 关系
- 分类:
- 有向图
- 无向图
- 加权图:边具有权重
NP完全问题 Non-deterministic Polynomial
- 多项式复杂程度的非确定性问题
- 简单定义:以难解著称的问题
- 经典案例
- 集合覆盖问题:为了解决合集覆盖问题,必须计算所有可能的集合(2^n)
- 商旅问题:n!
傅里叶变换
- 作用:压缩
数据结构
数组
- 实现:内存中连续的一段地址
- 优势:元素的定位
链表
- 实现:内存中散列的地址,每个元素都会存储下一个元素的地址
- 优势:元素的增删
栈 stack
- 操作:压入/弹出(栈帧 frame)
- 顺序:先进后出
- 应用场景:函数调用(调用栈,在Java中一个线程拥有一个调用栈)
/*
step:
1. greet("John") => push greet, name=John
2. print "Hi " + name => Hi John
3. greet2(name) => push greet2, name=John
4. "How are you, " + name => How are you, John
5. return => pop (greet2, name=John)
6. buy() => push buy
7. print "buy" => buy
8. return => pop (buy)
9. return => pop (greet, name=John)
*/
def greet(name):
print "Hi " + name;
greet2(name);
buy();
[return;]
def greet2(name):
print "How are you, " + name;
[return;]
def buy():
print "buy";
[return;]
- 缺点:使用递归会占用大量内存,可使用以下方式进行优化
- 使用循环
- 使用尾递归(C实现了,Java没有实现)
队列 queue
- 操作:入队/出队
- 顺序:先进先出
散列表 hash table
- 组成:键/值
- 实际应用
- 查找
- 防重
- 缓存
- 散列函数特性
- 将相同的输入生成相同的索引
- 将不同的输入生成不同的索引(理想)
- 已知数组的大小,不会返回无效索引
- 散列函数:SHA函数
- 冲突
- 解决:相同索引存储的是链表
- 避免:
2.1 较低的填装因子:【一般情况下】大于0.7需要调整山列表(数组)长度resizing;每次调整长度扩大一倍。
2.2 良好的散列函数
树
二叉查找树
- 时间复杂度:
- 平衡状态 O(log n)
- 最差情况 O(n)
概念
- 索引:元素的位置
- 填装因子:散列表包含的元素/位置总数
相关思想
递归
- 思想:分而治之
- 目的:让解决方案更清晰(并没有性能上的优势)
- 组成
- 基线条件 base case - 函数不再调用自己的条件,防止死循环
- 递归条件 recursive case - 函数调用自己的条件
分而治之 divide and conquer
- 步骤
- 找出尽可能的简单的基线条件
- 不断将问题分解(缩小整体规模),直到符合基线条件
贪婪算法
- 用于解决NP完全问题
- 思想:企图通过寻求局部最优解来获得全局最优解
动态规划
- 用于解决NP完全问题
- 适用
- 约束条件 给定的情况下优化某种指标
- 问题可分解为彼此独立且离散的子问题(网格中的一个单元格就代表一个子问题)
- 涉及网格且每个单元格中的值就是需要的最优解
- 思想:先解决子问题,再逐步解决大问题
- 实现:
- 网格
- 公式:根据需要的指标自行推导
涉及算法
选择排序
- 时间复杂度:O(n^2)
- 说明:实际为(n * 1/2 * n),数字不计入因此为n^2
- 过程:
- 第n次循环列表,获取最大的数max,将max放到新列表第n个位置
- 循环剩余的列表获取最大的,放到后面一个位置
- 重复2
快速排序
- 时间复杂度:n * log n
n - 每层需要的时间
log n - 调用栈的高度(最优;最差为n) - 过程
- 选一个元素作为基准
- 比基准小的作为新的组,重复1
- 比基准大的作为新的组,重复1
- 如果新的组只有0个或1个则停止
二分法
- 时间复杂度:O(log n)
- 输入:有序的元素列表
- 过程:
- 指针指向中间位置
- 获取指针位置的值并进行比较
2.1 等于 返回指针位置
2.2 大于 将上标指向指针位置-1(尽量缩小范围),重复执行
2.3 小于 将下标指向指针位置+1(尽量缩小范围),重复执行 - 当下标大于上标时,表示无匹配元素
广度优先搜索
- 适用:非权重图最短路径问题
- 时间复杂度:V + E
- V vertice 定点,出队&判断的时间消耗
- E edge 边,入队的时间消耗
- 理解
- 将起点入队
- 循环执行:从队列中出队,若不在搜索列表中则进行判断是否满足条件
- 若满足条件则得到结果
- 若不满足条件则将节点加入已搜索列表 & 将该结点的相关结点批量入队
狄克斯特拉算法 Dijkstra
-
适用:权重图(要求:有向/无环/无负权)的最短路径搜索问题
PS: 负权可能会导致已经处理过的节点再次更新 -
步骤
- 找到 未处理 过的最便宜的节点
- 更新该结点的邻居开销
- 重复1,直到计算图中所有的节点
-
理解
- 维护2个map,key均为节点名,map1 value为对应父节点用来追溯路径;map2 value为起点至该节点的最小花费
- 按顺序到达所有节点,并计算相应开销
-
代码
graph["Start"] = {};
graph["Start"]["A"] = 6;
graph["Start"]["B"] = 2;
graph["A"] = {};
graph["A"]["End"] = 1;
graph["B"] = {};
graph["B"]["A"] = 3;
graph["B"]["End"] = 5;
graph["End"] = {}
K最近邻算法 k-nearest neighbors
- 重点:挑选合适的特征进行比较
- 步骤
- 将数据量化成一组数字
- 根据公式找到相邻的n个数据(该过程为分类,得到的结果是一个编组)
- 通过编组进行运算得到预测结果(回归)
- 公式
- 距离公式
\sqrt{(a_1-a_2)^2 + (b_1-b_2)^2 + (c_1-c_2)^2 + \cdots + (z_1-z_2)^2}
- 余弦相似度