常见的数据结构与算法
数据结构
数组、链表、队列、栈、散列表、二叉树、堆、跳表、图、Trie树
满二叉树
节点要么没有子节点,要有就一定得有两个子节点
完全二叉树
从根往下数,除了最下层外都有两个子节点,而最下层所有叶子结点都向左边靠拢填满(构造一颗完全二叉树就是从上到下、从左往右的放置节点)
堆(Heap)
我们常用的堆是基于完全二叉树结构实现的
- 根节点的值一定是最大(或最小)的(或之一)
- 父结点的值必然大于(或小于)等于它的任何一个子结点
- 同层节点之间没有必然的大小关系
跳表
跳表──没听过但很犀利的数据结构
https://lotabout.me/2018/skip-list/
Trie树
又称字典树、前缀树、单词查找树、键树等,是一种多叉树结构,基本性质
- 根节点不包含字符,除根节点外的每一个子节点都包含一个字符
- 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串
- 每个节点的所有子节点包含的字符互不相同
算法思想
递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配
复杂度分析
- 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述
- 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述
时间复杂度
表示方法:大 O 符号表示法,即 T(n) = O(f(n)),并非衡量算法的真实执行时间,而是用来描述算法执行时间的增长变化趋势
常见的时间复杂度量级,复杂度依次递增,执行的效率越来越低:
- 常数阶 O(1)
- 对数阶 O(logN)
- 线性阶 O(n)
- 线性对数阶 O(nlogN)
- 平方阶 O(n^2)
- 立方阶 O(n^3)
- k 次方阶 O(n^k)
- 指数阶 O(2^n)
- 阶乘阶 O(n!)
常数阶 O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是 O(1);
即代码执行耗时不随着某个变量的增长而增长,即使代码成千上万行,都可以用 O(1) 来表示它的时间复杂度
对数阶 O(logN)
int i = 1;while (i < n) {i = i * 2;}
如上 while 循环里,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logN)
线性阶 O(n)
即算法的执行时间与某一变量的取值有关系
for(i=1; i<=n; ++i){j = i;j++;}
有没有更简单直接的方式来得出一段代码的时间复杂度呢? 有的,直接关注代码中循环执行次数最多的那段代码即可!
如上例,第2行代码和第3行代码显然就是循环执行次数最多的那段代码,那么𝑇(𝑛)=𝑂(2𝑛),去掉系数,即可快速得出𝑇(𝑛)=𝑂(𝑛)
线性对数阶 O(nlogN)
将时间复杂度为 O(logN) 的代码循环 n 遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)
for(m=1; m<n; m++){i = 1;while(i<n){i = i * 2;}}
平方阶 O(n^2)
即把时间复杂度为 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²)
for(x=1; i<=n; x++){for(i=1; i<=n; i++){j = i;j++;}}
同理,立方阶 O(n^3)、k 次方阶 O(n^k) 就相当于三层 n 循环、k 层 n 循环
阶乘阶 O(n!)
int factorial(int n) {for (int i = 0; i < n; i++) {factorial(n - 1);}}
循环执行次数最多的语句为 factorial(n - 1),在当前 n 下,会调用n次factorial(n - 1),而在每个 n−1 下,又会调用 n - 1次 factorial(n - 2),以此类推,得执行次数为 n×(n−1)×(n−2)×…×1,即 n!
最好时间复杂度、最坏时间复杂度、平均时间复杂度
空间复杂度
空间复杂度也不是用来计算程序实际占用的空间的,是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义;空间复杂度比较常用的有:O(1)、O(n)、O(n²)
空间复杂度 O(1)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
int i = 1;int j = 2;++i;j++;int m = i + j;
空间复杂度 O(n)
int[] m = new int[n]for(i=1; i<=n; ++i){j = i;j++;}
如上,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行 n 即可,即 S(n) = O(n)
空间复杂度O (n²)
递归函数反复调用自身 n 次,由于只有当函数调用到最后一层时才会结束一层调用,而之前的调用由于没有结束从而被一直压入栈中,导致空间无法得以重复利用,所以空间复杂度为𝑂(𝑛2)
注意:如果传递的是指针或引用,由于它们都会指向原本的内存空间,因此不需要分配新的存储单元
参考链接
算法的时间与空间复杂度(一看就懂) 不复杂的时间复杂度 https://blog.csdn.net/zjulyx1993?type=blog 算法
