数据结构之复杂度
数据结构的理解
数据本身是杂乱无章的,需要结构进行增删查改等操作更好的管理数据;
比如:在程序中需要将大量的代码(数据)通过结构进行管理;
再比如:定义1000个整型变量的数组,我们可以对数组进行删除某一个数据,在某一个数据后面插入新的数据等等操作。这些都是数据结构的体现。
数据结构是用来在计算机中存储和管理数据的,这种存储和管理的方式有很多种,我们后面会一一学到比如:线性表、数、图、哈希等
数组是最基础的数据结构;
算法理解
算法是一个良好计算的过程,我们可以将数据结构作为放着数据的容器,而算法就是用来如何才能让我们良好的从容器中获取和管理数据的这么一个过程就叫做算法
因此:有数据结构的地方就有算法,数据结构和算法不分家
算法重要性
在笔试和面试中是必考的,是以编程题的形式进行考察,我们要好好磨砺算法,做到手撕代码 <:
学好算法秘籍:
1.死磕代码
2.遇到算法题莫慌,带着思考进行画图。
3.看关于算法和数据结构的书藉
4.使用各大OJ平台进行刷题(用OJ平台刷题的魅力:只需要写入内部的逻辑代码即可,其他的平台都已经定义好了)
自己下去将轮转数组进行单独实现
如何衡量算法的好坏呢? ----->用复杂度进行衡量
复杂度理解
而复杂度又包含时间复杂度和空间复杂度
时间复杂度主要衡量一个算法的运行快慢;
空间复杂度主要衡量一个算法运行所需要的额外空间
随着时代的发展,我们对空间的关注已经不是大头了,时间才是,注意不是不关注!(也就是摩尔定律)
时间复杂度理解
时间复杂度是一个函数表达式T(N),这个与数学中的定义的一次函数和二次函数是一样的,它是用来衡量程序的运行效率。它是一个粗估的值,不是精确的值。
为什么不去计算程序的运行时间呢?
1.因为程序的运行时间和编译环境和运行机器的配置都有关系,
比如:同一个算法程序,用一个老的编译器进行编译和新的编译器编译,在同样的运行机器下程序运行时间不同
2.同一个算法程序,在不同的运行机器下(在一个老低配置机器和新高配置机器的情况下)使用同一个编译器进行编译,程序运行时间也会不一样
3. 并且计算程序的运行时间只能在程序运行之后才能知道,不能在写程序之前就知道大概的运行时间是多少。
总结:程序的运行时间是不确定的
时间复杂度T(N)到底怎么去计算呢?
T(N) = 每条语句的运行时间 * 运行次数
前面我们知道运行时间是不确定的,所以将运行时间去掉,所以
T(N) = 程序的运行次数
这个程序的时间复杂度T(N) = N^2 + 2N + 10
因为2N + 10 对时间复杂度的影响很小,所以忽略不计,最终:T(N) = N^2
因此,对于时间复杂度来说,是一个粗估的值,争对这种情况,我们有了大O的渐进表示法的计算法则进行计算
大O的渐进表示法
大O的计算规则:
- 时间复杂度T(N) 中,只保留最高项,去掉最低项,包括常数项
- 如果最高项的常数系数存在且不是1,则去除这个项目的常数系数
- 如果T(N) 中只有常数项,用常数1代表所有的加法常数。 O(1)来表示
判断高阶项的方法:对结果影响最大就是高阶项
大O的渐进表示法在空间和时间上都可以用
时间复杂度举例:
以下是常见时间复杂度的计算:
T(N) = 2N + 10 根据法则2:O(N)
如果T(N) 中存在两个变量,不确定这两个变量的大小,则这两个变量都可以表示,例如:O(M + N), M 和 N 都是变量,具体看题目中是如何定义M和N 的大小,比如:M >> N, 则为O(M),反之则为O(N);在有一种情况就是M == N, 则为O(M+N)。
T(N) = 100, 根据法则3:O(1)
请写出查找字符长度的时间复杂度
这里T(N) 的大小取决于你所要查找的位置,
比如:
查找的字符的位置在最后一个位置,则为O(N)
查找的字符的位置在第一个位置,则为O(1)
查找的字符的位置在中间位置,则为O(1/2 * N) = O(N)
最好情况: O(1)
最坏情况: O(N)
平均情况: O(N)
总结 通过上面我们会发现,有些算法的时间复杂度存在最好、平均和最坏情况。 最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数 最好情况:任意输入规模的最小运行次数(下界)
大O的渐进表示法在实际中一般情况关注的是算法的上界,也就是最坏运行情况。
在看看冒泡排序的时间复杂度:
因此,我们取得是上界的情况:O(N^2)
再看看这道题
这里需要注意的是:
课件中和书籍中 log2 n 、 log n 、 lg n 的表示
当n接近⽆穷⼤时,底数的⼤⼩对结果影响不⼤。因此,⼀般情况下不管底数是多少都可以省略不 写,即可以表⽰为 log n
不同书籍的表⽰⽅式不同,以上写法差别不⼤,我们建议使⽤ log n
还有一个原因是:键盘中敲不出底数
到这里时间复杂度的计算就够够用了
空间复杂度
空间复杂度计算规则基本跟时间复杂度类似,也使⽤⼤O渐进表⽰法。
注意:我们计算空间复杂度时只计算函数运⾏时所需要的栈空间,存储参数、局部变量、⼀些寄存器信息和非递归函数调用的栈帧等都是在函数运行时的内容。
因此空间复杂度主要通过函数在运⾏时候显式申请的额外空间来确定,也就是函数定义的下面那一部分
以下是常见的空间复杂度的计算:
常见的空间复杂度计算
通过动态内存申请内容也会涉及到空间复杂度的计算
例如:
常⻅复杂度对⽐:
总结:随着n的增加,各种的复杂度的变化趋势也大不相同;随着n的增加,变化越缓的复杂度代表着在程序运行时的效率高;越陡的效率低
关于复杂度的相关算法题:
轮转数组
思路1:
时间复杂度 O(n^2)循环K次将数组所有元素向后移动⼀位(代码不通过)
核心代码如下:
void rotate(int* nums, int numsSize, int k) {while(k--)//直到轮转结束后就停止{int end = nums[numsSize-1];for(int i = numsSize - 1; i > 0; i--)//这里是整体向后移的操作,起始位置在最后一位,直到将第一个数据移到第二位后就结束{nums[i] = nums[i - 1];//将一位的数据移到后一位}nums[0] = end;//将最后的移到第一位}
}
思路2:
空间复杂度 O(n)
申请新数组空间,先将后k个数据放到新数组中,再将剩下的数据挪到新数组中
核心代码:
void rotate(int* nums, int numsSize, int k) {int NewArr[numsSize];for(int i = 0; i < numsSize; i++){NewArr[(k + i) % numsSize] = nums[i];//这里是将原数组中的数据放入到新数组下标为(k + i) % numsSize中}for(int i = 0; i < numsSize; i++){nums[i] = NewArr[i]; //再放到原数组中去}}