开篇词
- 王争自己的算法之路
- 基础一定要牢固 那些看起来的新技术核心和本质都是当初学的那些东西
基础知识就像一座大楼的地基 它决定了我们的技术高度 要想快速的做出点事情 前提条件一定是基础能力过硬 “内功要到位” - 课程分为四个递进的模块
- 入门篇
- 基础篇
- 高级篇
- 实战篇
01为什么要学习数据结构和算法
- 要想通过大厂面试 前往别让数据结构和算法拖了后腿
我们学任何知识都是了“用”的,是为了解决实际工作问题的 - 掌握数据结构和算法 不管对阅读框架源码还是理解其背后的设计思想都是非常有用的 业务开发工程师难道真的想做一辈子的CRUD boy吗?
- 基础架构研发工程师 写出达到开源水平的框架才是目标
- 自己只要还有追求 不想别行业淘汰 那就不要写凑活能用的代码
掌握了数据结构与算法 你看待问题的深度解决问题的角度就会完全不一样
02 如何抓住重点 系统高效地学习数据结果与算法
- 什么是数据结构 算法
数据结构就是指一组数据的存储结构 算法就是操作数据的一组方法 - 数据结构是为算法服务的,算法要作用在特定的数据结构上
- 学习方法:
- 边学边练 适度刷题
- 多问 多思考 多互动
- 打怪升级学习法
给自己设置一个切实可行的目标 - 知识需要沉淀 不要想试图一下子掌握全部
学习的过程是反复迭代,不点沉淀的过程
03 复杂度分析 如何分析、统计算法的执行效率和资源消耗
原文作者认为:
复杂度分析是整个算法学习的精髓 只要掌握了它 数据结构和算法的内容就掌握了一半
- 事后统计法 先将数据跑一遍 然后统计监控
- 测试结果依赖测试环境
- 测试结果受数据规模的影响较大
- 大O表示法
- 假设每行代码的执行时间都是一样的
- 所有代码的执行时间T(n)与每行代码的执行次数成正比
- 大O时间复杂度实际上并不具体代表代码真正的执行时间,代表执行时间随数据规模增长的变化趋势,也叫渐进时间复杂度,简称时间复杂度。
- 加法法则
总的时间复杂度等于量级最大的那段代码的时间复杂度 - 乘法法则
嵌套代码的复杂度等于嵌套内外代码复杂度的乘积 -
常见时间复杂度实例分析
常见时间复杂度实例分析 - O(1)
一般情况下,算法中不存在循环语句、递归语句,即使有成千上万的代码其时间复杂度也是O(1) - O(logn) O(nlogn)
i=1;
while (i <= n)
{ i = i * 2
}
O(log2n)
log3n = log3 2 * log2 n
在采用大O标记复杂度的时候可以忽略系数 即O(Cf(n)) = O(f(n))所以O(log2n)等于O(log3n)。因此在对数阶时间复杂度的标示方法里,我们忽略对数的“底”,统一表示为O(logn)
- O(nlogn)
如果一段代码的时间复杂度是O(logn),循环N遍,那么时间复杂度就是O(nlogn) - O(m+n)
m和n是表示两个的数据规模,无法事先评估m和n谁的量级大,所以就不能使用加法法则省略掉其中一个 -
空间复杂度
表示算法的存储空间与数据规模之间的增长关系。
我们常见的空间复杂度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。
04 复杂度分析(下)浅析最好、最坏、平均、均摊时间复杂度
最好情况时间复杂度就是,在最理想的情况下执行这段代码的时间复杂度
最坏情况时间复杂度就是在最糟糕的情况下执行这段代码的时间复杂度
平均情况时间复杂度
- 要查找的变量x在数组的位置,有n+1种情况,在数组的0~n-1位位置种和不在数组中。我们把每种情况下,需要查找遍历的元素个数累计起来,然后除以n+1,就可以得到需要遍历的元素个数的平均值
image.png
我们知道,时间复杂度的大O标记法中,可以忽略掉系数,低阶,常量,咱们把刚刚这个公式简化之后,得到的锁屏键时间复杂度就是O(n)。
这个结论虽然是正确的,但是计算过程稍微有点问题。刚刚的n+1种情况,出现的概率并不是一样的。
我们知道,要查找的变量x,要么在数组里,要么就不在数组里。这两种情况对应的概率统计起来很麻烦,我们假设在数组中与不在数组中的概率都是1/2.另外要查找的数据出现在0~n-1中的任意位置的概率就是1/(2n)。
所以我们前面的推导的最大问题就是没有将各种情况发生的概率考虑进去。如果我们把每种情况发生的概率也考虑也考虑进入,那么平均时间复杂度的计算过程就变成了:
image.png
这个值就是概率论中的加权平均值,也叫做期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度。
引入概率之后,前面那段代码的加权平均值为(3n+1)/4.用大O表示法来表示,去掉系数和常量,这段代码的加权时间复杂度仍然是O(n)。
均摊时间复杂度
// array 表示一个长度为 n 的数组
// 代码中的 array.length 就等于 n
int[] array = new int[n];
int count = 0;
void insert(int val) {
if (count == array.length) {
int sum = 0;
for (int i = 0; i < array.length; ++i) {
sum = sum + array[I];
}
array[0] = sum;
count = 1;
}
array[count] = val;
++count;
}
这段代码实现了一个往数组中插入数据的功能。当数组满了之后,也就是代码中的count == array.lenth 时,我们用for循环遍历数组求和,并清空数组,将求和之后的sum值放到数组的第一个位置,然后再将新的数据插入。如果数组一开始就有空闲空间,则直接经数据插入数组。
- 最好情况
数组中有空闲空间,我们只需要将数组插入到数组下标为count的位置就可以了,所以最好的时间复杂度为O(1)。 - 最坏的情况下,数组中已经没有空间了,我们需要先做一次数组的遍历求和,然后将数组插入。最坏的情况时间复杂度为O(n)。
-
平均时间复杂度
O(1)
假设数组的长度时n,根据数据插入的位置的不同,我们可以分为n中情况,每种情况的时间复杂度时O(1)。除此之外,还有一种‘额外’的情况,那就是在数组没有空闲空间时插入一个数据。这个时候的时间复杂度是O(n)。而且,这n+1种情况发生的概率一样,都是1/(n+1)。所以,根据加权平均的计算方法,我们求得的平均时间复杂度就是
image.png
每一次O(n)的插入操作,都会跟着n-1次的O(1)的插入操作,所以把耗时多的那次操作均摊到接下来的n-1次耗时少的操作上,均摊下来,这一组的操作的均摊时间复杂度就是O(1)。这就是均摊分析的大致思路。
均摊时间复杂度应用场景比较特殊,不会经常遇到,应用场景:
对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的关系,这个时候我们可以将这一组操作放在一块分析,看是否能将较高时间复杂度的那次操作的耗时平摊到其他那些时间复杂度比较低的操作上。而且在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度
05 编程语言只数组从从0开始的原因
历史原因:c语言是从0开始的 大部分语言都借鉴了c语言
寻址方便:
a[i] address = start + i *(date_type_size)
else
a[i] address = start + (i - 1) *(date_type_size)
06 链表:实现LRU缓存淘汰算法
- 线性表:数组 队列 链表
- 非线性表: 树 图
网友评论