数据结构与算法
1.为什么要学习数据结构与算法?
掌握数据结构和算法,不管对于阅读框架源码,还是理解其背后的设计思想,都是非常有用的!
2.什么是数据结构与算法?
从广义上讲,数据结构就是指一组数据的存储结构,算法就是操作数据的一组方法!
从狭义上讲,就是指常见的一些数据结构和算法,如队列,栈,堆,二分查找,动态规划等.
数据结构和算法是相辅相成的,数据结构是为算法服务的,算法是作用在特定的数据结构之上,因此,无法孤立数据结构来学习算法,也无法孤立算法来学习数据结构!
3.复杂度分析
复杂度分析是数据结构与算法中最重要的一个概念,学会了它,就学会了数据结构与算法的精髓!
3.1为什么需要复杂度分析?
通过统计,监控等就能获取到算法的执行时间和占用的内存大小,为什么还需要做空间,时间复杂度分析呢?
统计,监控这种方式叫做事后统计法,这种统计方法有很大的局限性:
- 测试结果非常依赖测试环境;
- 测试结构受数据规模的影响比较大.
所以需要一种不需要执行就能获取执行时间和空间的方式,这就是时间及空间复杂度!
3.2大O复杂度表示法
估算以下代码的执行时间
int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
// T(n)= O(2n+2)
假设每一行代码的执行时间都是unit_time,
第2,3行代码都是1个unit_time,第4,5行都是n个unit_time,代码的总执行时间是
(2n+2)*unit_time 所以 代码的执行时间 T(n)与每行代码的执行次数成正比.
这个规律就是大O表示法
T(n) = O(f(n))
T(n)表示代码的执行时间 , n表示数据规模的大小,f(n)表示每行代码执行的次数总和.
因为这是一个公式,所以用f(n)表示,公式中的O表示执行时间T(n)与f(n)表达式成正比.
当n很大时,比如10000,100000,而公式中的低阶,常亮,系统三部分并不左右增长趋势,所以都忽略.只需要记录一个最大量级即可!
所以以上例子的时间复杂度,可以记为: T(n) = O(n).
3.2.1时间复杂度分析
A. 只关注循环执行次数最多的一段代码.
B. 加法法则:总复杂度等于量级最大的那段代码的复杂度.
int cal(int n) {
int sum_1 = 0;
int p = 1;
for (; p < 100; ++p) {
sum_1 = sum_1 + p;
}
int sum_2 = 0;
int q = 1;
for (; q < n; ++q) {
sum_2 = sum_2 + q;
}
int sum_3 = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum_3 = sum_3 + i * j;
}
}
return sum_1 + sum_2 + sum_3;
}
时间复杂度 T(n)= T1(n)+T2(n) = max(O(f(n)),O(g(n)) = O(max(f(n),g(n))).
C. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
int cal(int n) {
int ret = 0;
int i = 1;
for (; i < n; ++i) {
ret = ret + f(i);
}
}
int f(int n) {
int sum = 0;
int i = 1;
for (; i < n; ++i) {
sum = sum + i;
}
return sum;
}
时间复杂度T(n)=T1(n)* T2(n) = O(n*n)=O(n2)
3.2.2常见的时间复杂度实例分析
A. 多项式量级
O(1) : 一般情况下,只要算法中不存在循环语句,递归语句,即使有成千上万行的代码,其时间复杂度也是O(1)
O(logn),O(nlogn) : 对数阶时间复杂度非常常见,同时也是最难分析的一种.
i=1;
while (i <= n) {
i = i * 2;
}
从代码中可以看到, 变量i从1开始取,每循环1次就乘以2,当大于n时,循环结束.这就是等比数列
x=log2n ,所以这个代码时间复杂度是O(log2n)
在对数阶时间复杂度的表示方法里,忽略对数的底,统一表示为O(logn).
O(m+n),O(m*n) : 代码是由两个数据的规模决定的
int cal(int m, int n) {
int sum_1 = 0;
int i = 1;
for (; i < m; ++i) {
sum_1 = sum_1 + i;
}
int sum_2 = 0;
int j = 1;
for (; j < n; ++j) {
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
以上代码时间复杂度为O(m+n)
B. 非多项式量级
O(2n) 和 O(n!)
当数据规模越来越大时,非多项式量级算法的执行时间会急剧增加,求解问题的执行时间会无线增大,所以非多项式量级算法是非常低效的算法.
3.2.3空间复杂度分析
时间复杂度的全程是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系.
空间复杂度全程就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系.
常见的空间复杂度就是O(1),O(n),O(n2)
3.2.4最好、最坏、平均、均摊时间复杂度
// n 表示数组 array 的长度
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
- 最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度.例子中是O(1).
- 最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度.例子中是O(n).
- 平均情况时间复杂度, 以以上例子来说, 查找变量x在数据中的位置,有n+1种情况,在数据的0~n-1位置上和不在数组中, 把每种情况下查找遍历的元素个数累加,再除以n+1就是需要遍历元素的平均值.
公式简化以后,就是**平均时间复杂度**O(n).
以上公式的最大问题是没有将各种情况发生的概率统计进去,公式可以修改为
简化以后仍然是O(n),这个值是 加权平均复杂度或者期望时间复杂度. - 均摊时间复杂度就是一种特殊的平均时间复杂度, 当对一个数据结构进行操作时,大部分情况时间复杂度都很低,只有个别情况下时间复杂度比较高,而且存在前后之间的时序关系,这是可以将较高时间复杂度那次操作的耗时,平摊到其他时间复杂度比较低的操作上. 这种分析方法叫做 摊还分析法, 通过这种方法分析的时间复杂度叫做 均摊时间复杂度.
4.数据结构和算法概述
以下是数据结构与算法的知识点
5.常见的数据结构和算法
下面主要学习一些常见的数据结构与算法:
10个数据结构: 数组,链表,栈,队列 ,散列表,二叉树,堆,跳表,图,Trie树.
10个算法:
递归,排序,二分查找,搜索,哈希算法,贪心算法,分治算法
,回朔算法,动态规划,字符串匹配算法.
后续章节会详细的讲解这10种数据结构和10种算法.