文章目录
- 1.常用数据结构与算法
- 2.复杂度分析
- 大O复杂度表示法
- 时间复杂度
- 空间复杂度
1.常用数据结构与算法
数据结构是指一组数据的存储结构。算法就是操作数据的一组方法。
数据结构与算法相辅相成,数据结构为算法服务,算法要作用在特定的数据结构上。
- 常用数据结构:
- 数组、链表、栈、队列
- 散列表
- 二叉树、堆、 图
- 跳表
- Trie树
- 常用算法:
- 递归
- 排序
- 二分查找
- 搜索
- 哈希算法
- 贪心算法
- 分治算法
- 回溯算法
- 动态规划
- 字符串匹配算法
对于以上常用的数据结构预算法,在这里仅作简单记录,后续学习过程中会对每一种数据结构及算法单独写文章记录。
2.复杂度分析
设计优异的数据结构能够节省更多的空间,而优异的算法能够节省更多的时间。因此,学习数据结构与算法的核心就是尽可能让代码运行的更快、更省存储空间。
大O复杂度表示法
- 公式:T (n) = O (f(n)) ,其中T(n) 代表的是代码的执行总时间,f(n)表示的是每行代码执行的次数总和。而公式中的O 表示代码的执行时间T (n) 与 f(n)表达式成正比。ps:n代表的是每行代码的执行次数
- 大O时间复杂度并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,也叫做渐进时间复杂度,简称时间复杂度。
- 示例:
public static int calSum(int n) {
int sum = 0; //执行1次
int i = 1; //执行1次
int j = 1; //执行1次
for (; i <= n; i++) { //执行n次
j = 1; //执行n次
for (; j <= n; j++) { //执行n*n次
sum += i * j; //执行n*n次
}
}
return sum;
}
假设每行代码执行都需要1个unit_time时间,则以上代码执行所需时间T(n) = (2n2 + 2n + 3)*unit_time,即f(n) = 2n2 + 2n + 3
时间复杂度
如何分析一段代码的时间复杂度?
- 只关注循环执行次数最多的一段代码
public static void calSum2(int n) {
int sum = 0; //只执行1次
for (int i = 0; i < n; i++) { //执行n次
sum += i; //执行n次
}
}
如上代码中,int sum = 0; 只执行1次且与n无关,for循环中的代码执行了n次,所以总的时间复杂度就是 O(n).
- 加法法则:总复杂度等于量级最大的那段代码的复杂度
public static int calSum(int n) {
int sum = 0;
int i = 1;
int j = 1;
// 循环1
for (int k = 0; k < 1000; k++) {
sum += k;
}
//循环2
for (int k = 0; k < n; k++) {
sum += k;
}
//循环3
for (; i <= n; i++) {
j = 1;
for (; j <= n; j++) {
sum += i * j;
}
}
return sum;
}
上述代码中,循环1执行了1000次,是一个常量的执行时间,与n的规模没有关系。循环2的时间复杂度为O(n),循环3的时间复杂度伪O(n2)。总复杂度等于量级最大的那段代码的复杂度,因此以上代码的时间复杂度为O(n2)。
即:T1(n) = O(f(n)),T2(n) = O(g(n));
则T(n)=T1(n)+T2(n)=max(O(fn),O(g(n)))=O(max(f(n),g(n)));
3.乘法法则: 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
参照加法法则,如果T1(n)=O(f(n)),T2(n)=O(g(n));
则 T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n));
public static void calSum2(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += getSum(i);
}
}
public static int getSum(int n){
int sum = 0;
for (int i = 0; i < n; i++) {
sum+=i;
}
return sum;
}
上述代码中calSum2() 方法时间复杂度为O1(n),方法getSum() 时间复杂度也是O2(n),由于方法getSum()被嵌套在calSum2()方法中调用,因此calSum2()方法的时间复杂度为O(n) = O1(n)*O2(n) = O(n2);
4. 常见时间复杂度
- 时间复杂度表
常见时间复杂度 | 自上而下递增 |
常量阶 | O(1) |
对数阶 | O(logn) |
线性阶 | O(n) |
线性对数阶 | O(nlogn) |
平方阶 | O(n2) |
立方阶 | O(n3) |
k次方阶 | O(nk) |
指数阶 | O(2n) |
阶乘阶 | O(n!) |
- 常见复杂度分析
- 常量阶O(1)
如下代码中,执行时与数量级n无关,则其时间复杂度为O(1);只要代码的执行时间不随n的增大而增大,则其时间复杂度都是O(1);一般情况下,只要代码中没有循环语句、递归语句,即是代码量再多,其时间复杂度也是O(1);
int m = 10;
int k = 6;
int sum = m * k;
- 对数阶O(log(n))
public static int getSum(int n) {
int sum = 0, i = 1;
while (i < n) {
sum += i;
i = i * 3;
}
return sum;
}
在上述代码中,当i>=n时推出循环,每次循环i都乘3,假设循环次数为k,则i=3k;达到循环退出的临界值时i >= n ,即3k >= n,等号两边同时取以3为底的对数,可得 k = ,即以上代码的时间复杂度为;
ps:对数之间是可以互相转换的, = * = * ,而在计算时间复杂度的时候,系数是可以忽略不计的,所以 可以看做是;同理,在计算时间复杂度的时候,不管是以几为底的对数,都可以写作O()
- 线性对数阶O(nlogn)
如果一段代码的时间复杂度是,而这段代码又执行了n遍,那么总的时间复杂度就是.如下代码所示:
public static int getSum(int n) {
int sum = 0, i = 1;
for (int j = 0; j < n; j++) {
while (i < n) {
sum += i;
i = i * 3;
}
}
return sum;
}
- O(m+n)、O(m * n)
这种形式的时间复杂度是由两个数据规模决定的。我们无法判断m和n哪一个数据量级大,因此在计算时间复杂度的时候无法使用加法法则省略掉其中一个,因此时间复杂度会出现O(m+n)的情况。但是乘法法则同样是适用的.
补充:
- 最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度
- 最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度
- 平均时间复杂度:最好、最坏情况出现的概率极低,为了将各种情况都考虑进去,引入平均时间复杂度。
- 均摊时间复杂度
空间复杂度
- 空间复杂度分析可以参照时间复杂度分析方法使用大O表示法表示;空间复杂度表示算法的存储空间与数据规模之间的增长关系。
- 常见的空间复杂度有:O(1)、O(n)、O(n2),对数阶复杂度基本用不到
文章仅用作学习过程记录,如有不当,欢迎交流指正~