数据结构-复杂度
复杂度
- 1.数据结构
- 1.1算法
- 2.算法效率
- 2.1复杂度的概念
- 3.时间复杂度
- 3.1大O渐进表示法
- 3.2时间复杂度计算示例
- 3.2.1 示例1
- 3.2.2 示例2
- 3.2.3 示例3
- 3.2.4 示例4
- 3.2.5 示例5:
- 3.2.6 示例6
- 3.2.7 示例7
- 4.空间复杂度
- 4.1.1 示例1
- 4.1.2 示例2
- 5.常见复杂度对比
- 6.复杂度算法题
- 6.1旋转数组
1.数据结构
数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。没有一种单一的数据结构对所有用途都有用,所以我们要学各式各样的数据结构。
不仅能存储数据,还要能够管理数据。
1.1算法
算法就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。
那么怎么样的算法算是好的呢?好算法是用什么来衡量的?
我们看看下面的代码:
#include<stdio.h>
int main()
{int t1 = clock();//表示计算代码当前所用时间for (int i = 0; i < 100000; i++){for (int j = 1; j < 10000; j++){int a = 1;}}int t2 = clock();printf("%d\n", t2 - t1);return 0;
}
上面这个代码运行结果是不同的,这和电脑的配置是相关的。所以看算法的执行时间是不行的。
2.算法效率
那么如何衡量一个算法的好坏呢?
我们来观察一个案例:
https://leetcode.cn/problems/rotate-array/description/
void rotate(int* nums, int numsSize, int k) {while(k--)//k有几次轮转几次。{int end = nums[numsSize-1];//将最后一个元素拿出来for(int i = numsSize - 1;i > 0 ;i--){nums[i] = nums[i-1];//将前一个元素放到后一个}nums[0] = end;//将拿出来的最后一个元素,放到前面。}
}
解释:
输入:num = [1,2,3,4,5,6,7],k = 3
输出:[5,6,7,1,2,3,4]
向后轮转1步:[7,1,2,3,4,5,6]
向后轮转2步:[6,7,1,2,3,4,5]
向后轮转3步:[5,6,7,1,2,3,4]
思路:循环k次将数组所有元素向后移动一位
点击执行可以通过,然而点击提交却无法通过,那该如何衡量其好与坏呢?
2.1复杂度的概念
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到;额很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
3.时间复杂度
定义:在计算机科学中,算法的时间复杂度是一个函数式T(N),N是影响时间复杂度的输入条件,它定量描述了该算法的运行时间。时间复杂度是衡量程序的时间效率,那么为什么不去计算程序的运行时间呢?
1.因为程序运行时间和编译环境和运行机器的配置都有关系,比如同一个算法程序,用一个老编译器进行编译和新的编译器编译,在同样机器下运行时间不同。
~
2.同一个算法程序,用一个老底配置和高配置机器,运行时间也不同。
~
3并且时间只能程序写好后测试,不能写程序前通过理论思想计算评估。
这个T(N)函数式计算了程序的执行次数。那么我们通过程序代码或者理论思想计算出程序的执行次数的函数式T(N),假设每句指令执行时间基本一样(实际中有差别,但是微乎其微),那么执行次数和运行时间就是等比正相关,这样也脱离了具体的编译运行环境。执行次数就可以代表程序时间效率的优劣。比如解决一个问题的算法a程序T(N),算法b程序T(N)= N^2,那么算法a的效率一定优于算法b。
影响时间复杂度的条件有:每条语句的执行时间*每条语句的执行次数
每条语句的执行时间(无法给出准确数据;给出结论:每条语句的执行时间即使有差别但是微乎其微,可以忽略不计,认为每条语句的执行时间是相同的。)
案例:
请计算⼀下Func1中++count语句总共执⾏了多少
次?
void Func1(int N)
{ int count = 0; for (int i = 0; i < N ; ++ i) { for (int j = 0; j < N ; ++ j) { ++count; } } for (int k = 0; k < 2 * N ; ++ k) { ++count; } int M = 10; while (M--) { ++count; }
}
根据代码中的执行的基本操作次数:
T (N) = N + 2*N + 10
可以看出对结果影响最大的是N^2。
时间复杂度只能用来表示输入条件对事件的影响趋势
实际中我们计算时间复杂度时,计算的也不是程序的精确的执行次数,精确执行次数计算起来还是很麻烦的(不同的一句程序代码,编译出的指令条数是不一样的),计算出精确的执行次数意义也不大,因为我们计算时间复杂度只是想比较算法程序的增长量级,也就是当N不断变大时T(N)的差别,上面我们已经看到了当N不断变大时常数和低阶项对结果的影响很小,所以我们只需要计算程序能代表增长量级的大概执行次数,复杂度的表示通常使用大O的渐进表示法
3.1大O渐进表示法
大O符号:是用于描述函数渐进行为的数学符号
推导大O阶规则:
1.时间复杂度函数式T(N)中,只保留最高阶项,去掉那些低阶项,因为当N不断变大时,低阶项对结果影响越来越小,当N无穷大时,就可以忽略不计了
~
2.如果最高阶项存在且不是1,则去除这个项目的常熟系数,因为当N不断变大,这个系数对结果影响越来越小,当N无穷大时,就可以忽略不计了。
~
3.T(N)中如果没有N相关的项目,只有常数项,用常数1取代所有加法常数。
3.2时间复杂度计算示例
3.2.1 示例1
void Func2(int N)
{ int count = 0; for (int k = 0; k < 2 * N ; ++ k) { ++count; } int M = 10; while (M--) { ++count; } printf("%d\n", count);
}
Func2执行的基本操作次数:
T(N)= 2*N + 10
根据推导规则第3条得出:
复杂度为:O(N)
数据结构中只考虑变化对时间复杂度的影响。
3.2.2 示例2
void Func3(int N, int M)
{ int count = 0; for (int k = 0; k < M; ++ k) { ++count; } for (int k = 0; k < N ; ++
k) { ++count; } printf("%d\n", count);
}
Func3执行的基本操作次数:
T(N) = M + N
有两个可变条件。需要进一步讨论:
M >> N : O(M)
N >> M : O(N)
N == M : O(M)或者O(N)
3.2.3 示例3
void Func4(int N)
{ int count = 0; for (int k = 0; k < 100; ++ k) { ++count; } printf("%d\n", count);
}
T(N)= 100
根据推导规则第1条得出
时间复杂度为:O(1)
这里的1不是执行一次,而是表示常数
无论常数是多少,常数对时间的增长趋势没有任何影响
3.2.4 示例4
const char * strchr ( const char
* str, int character)
{const char* p_begin = s;while (*p_begin != character){if (*p_begin == '\0')return NULL;p_begin++;
}return p_begin;}
假设字符串长度为n:
查找的是前面的字符:查找常数次
查找的是后面的字符:查找n次
查找的是中间的字符:查找n/2,就是n次。
对于当前的时间复杂度来说,我们要划分为不同的场景:
查找的是前面的字符,时间更少,那么我们的时间复杂度就更优一些,就称之为最好的情况。所以上面的三种情况可以被分为:最好情况、最坏情况和平均情况。
因此时间复杂度可以写为:
最好情况:O(1)
最坏情况:O(N)
平均情况:O(N)
总结
通过上面我们会发现,有些算法的时间复杂度存在最好、平均和最坏情况
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
大O的渐进表示法在实际中一般情况关注的是算法的上界,也就是最坏运行情况。
3.2.5 示例5:
void BubbleSort(int* a, int n)
{ assert(a); for (size_t end = n; end > 0; --end) { int exchange = 0; for (size_t i = 1; i < end; ++i) { if (a[i-1] > a[i]) { Swap(&a[i-1], &a[i]); exchange = 1; } } if (exchange == 0) break; }
}
上面的代码为冒泡排序。
分析:
(1)若数组有序,则:
T(N)= N
(2)若数组有序且为降序,则:
T(N)=N*(N+1)/2
当数组有序的时候 只需要比较n-1
即冒泡排序时间复杂度最好的情况为O(n)
最差的情况,时间复杂度为:O(n^2)
只要有一个是乱序,就是最差的情况
3.2.6 示例6
void func5(int n)
{int cnt = 1;while (cnt < n){cnt *= 2;}
}
当n=2时,执行次数为1
当n=4时,执行次数为2
当n=16时,执行次数为4
假设执⾏次数为x ,则2^x = n
因此执⾏次数:x = log n
因此:时间复杂度最差情况为:O(log2n)
注意:
注意log2n、logn、lgn的表示
当n接近无穷大时,底数的大小对结果影响不大。因此,一般情况下不管底数是多少都可以省略不写,即可以表示为logn
3.2.7 示例7
long long Fac(size_t N)
{ if(0 == N) return 1; return Fac(N-1)*N;
}
调用一次Fac函数的时间复杂度为O(1)
而在Fac函数中,存在n次递归函数调用Fac函数
因此:
时间复杂度为:O(n)
4.空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中因为算法的需要额外临时开辟的空间。
空间复杂度不是程序占用了多少bytes的空间,因为常规情况每个对象大小差异不会很大,所以空间复杂度算的是变量的个数。
空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显示申请的额外空间来确定。
4.1.1 示例1
void BubbleSort(int* a, int n)
{ assert(a); for (size_t end = n; end > 0; --end) { int exchange = 0; for (size_t i = 1; i < end; ++i) { if (a[i-1] > a[i]) { Swap(&a[i-1], &a[i]); exchange = 1; } } if (exchange == 0) break; }
}
函数栈帧在编译期间已经确定好了,只需要关注函数在运行时额外申请的空间。
BubbleSort额外申请的空间有exchange等有限个局部变量,使用了常数个额外空间
在函数体里面是运行时确定申请空间。
因此空间复杂度为O(1)
4.1.2 示例2
long long Fac(size_t N)
{ if(N == 0) return 1; return Fac(N-1)*N;
}
Fac递归递归调用了N次,额外开辟了N个函数栈帧,每个栈帧使用了常数个空间
因此空间复杂度为:O(N)
时间复杂度和空间复杂度存在部分相同,因为,时间复杂度求解的是执行次数,空间复杂度申请的是空间。
5.常见复杂度对比
从左到右复杂度越优。
6.复杂度算法题
6.1旋转数组
https://leetcode.cn/problems/rotate-array/description/
思路一:
时间复杂度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;}
}
时间复杂度为O(n^2)
空间复杂度为O(1)
该代码超出时间复杂度
思路二:
空间复杂度O(n)
申请新数组空间,先将后k个数据放到新数组中,在将剩下的数据挪到新数组中
void rotate(int* nums, int numsSize, int k)
{int newArr[numsSize];for (int i = 0; i < numsSize; ++i) {newArr[(i + k) % numsSize] = nums[i];}for (int i = 0; i < numsSize; ++i) {nums[i] = newArr[i];}
}
时间复杂度 O(N)
空间复杂度 O(N)
以空间换时间的方式来提高算法性能
思路三:
空间复杂度O(1)
• 前n-k个逆置:4 3 2 1 5 6 7
• 后k个逆置:4 3 2 1 7 6 5
• 整体逆置:5 6 7 1 2 3 4
void reverse(int* nums,int begin,int end)
{while(begin<end){int tmp = nums[begin];nums[begin] = nums[end];nums[end] = tmp;begin++;end--;}
}
void rotate(int* nums, int numsSize, int k)
{k = k%numsSize;reverse(nums,0,numsSize-k-1);reverse(nums,numsSize-k,numsSize-1);reverse(nums,0,numsSize-1);
}
相关文章:

数据结构-复杂度
复杂度 1.数据结构1.1算法 2.算法效率2.1复杂度的概念 3.时间复杂度3.1大O渐进表示法3.2时间复杂度计算示例3.2.1 示例13.2.2 示例23.2.3 示例33.2.4 示例43.2.5 示例5:3.2.6 示例63.2.7 示例7 4.空间复杂度4.1.1 示例14.1.2 示例2 5.常见复杂度对比6.复杂度算法题6…...

无人机之放电速率篇
无人机的放电速率是指电池在一定时间内放出其储存电能的能力,这一参数对无人机的飞行时间、性能以及安全性都有重要影响。 一、放电速率的表示方法 放电速率通常用C数来表示。C数越大,表示放电速率越快。例如,一个2C的电池可以在1/2小时内放…...

免费开源AI助手,颠覆你的数字生活体验
Apt Full作为一款开源且完全免费的软件,除了强大的自然语言处理能力,Apt Full还能够对图像和视频进行一系列复杂的AI增强处理,只需简单几步即可实现专业级的效果。 在图像处理方面,Apt Full提供了一套全面的AI工具,包…...

VMware虚拟机三种网络模式详解
主要内容 1. 桥接模式2. NAT模式VMware Network Adapter VMnet8虚拟网卡的作用 3. 仅主机模式VMware Network Adapter VMnet1虚拟网卡的作用设置虚拟机联通外网 4. 总结 参考资料: 1.Vmware虚拟机三种网络模式详解 VMware虚拟机三种网络模式详解之Bridged࿰…...

【算法篇】动态规划类(4)——子序列(笔记)
目录 一、Leetcode 题目 1. 最长递增子序列 2. 最长连续递增序列 3. 最长重复子数组 4. 最长公共子序列 5. 不相交的线 6. 最大子序和 7. 判断子序列 8. 不同的子序列 9. 两个字符串的删除操作 10. 编辑距离 11. 回文子串 12. 最长回文子序列 二、动态规划总结 …...

【图解版】力扣第162题:寻找峰值
注意 题目只要求找到一个峰值就可以了。nums[-1]和nums[n]这两个位置是负无穷,也就是说,除了数组的位置之外,其它地方都是负无穷。对于所有有效的 i 都有 nums[i] ! nums[i 1] 方法一 遍历整个数组,找到最高的那个点。时间复杂…...

Windows电脑桌面如何弄个好用的提醒备忘录?
在这个充满挑战的时代,每个人都渴望成为更好的自己。然而,随着生活节奏的加快,我们时常发现自己陷入了各种琐事之中,难以脱身。为了不让重要的事情被遗漏,一款好的提醒备忘录工具就显得尤为关键。那么,Wind…...

Windows API 一 ----起步
目录 1.介绍主函数入口参数。 2. 简单介绍 Windows.h 这个头文件 小结,也聊一聊 1.介绍主函数入口参数。 第一个参数: HINSTANCE 类型的 参数, 称为“实例句柄“,这个参数唯一标志了我们写的这个程序。 第二个参数: HINSTANCE…...

音视频入门基础:H.264专题(19)——FFmpeg源码中,获取avcC封装的H.264码流中每个NALU的长度的实现
一、引言 从《音视频入门基础:H.264专题(18)——AVCDecoderConfigurationRecord简介》中可以知道,avcC跟AnnexB不一样,avcC包装的H.264码流中,每个NALU前面没有起始码。avcC通过在每个NALU前加上NALUnitL…...

【uniapp】设置公共样式,实现公共背景等
目录 1、 全局渐变背景色 2.1 创建common目录 2.2 在common下新建style和images等目录 2.3 在style下新建common-style.scss 2.4 common-style输入全局渐变颜色 2.5 引入样式 2.6 业务页面引入 2.7 展示 2、全局字体颜色 2.1 新建base-style.scss文件 2.2 设置base-…...

Node.js学习笔记
回顾: javascript 可以在浏览器运行 (js代码会JavaScript的解析引擎执行)chrome 》V8 (性能最好)FireFox 》 奥丁猴safri 》JSCoreIE浏览器 》查克拉JavaScript可以在浏览器端操作DOM 和BOM每一个浏览器都内置了B…...

resnetv1骨干
# 普通的卷积残差块 def apply_basic_block( x, filters, kernel_size3, stride1, conv_shortcutTrue, nameNone ): # 预设块名称前缀 if name is None: name f"v1_basic_block_{keras.backend.get_uid(v1_basic_block_)}" # 设置残差连接前…...

设计模式,面试级别的详解(持续更新中)
设计模式,面试级别的详解(持续更新中) 软件的设计原则 常⽤的⾯向对象设计原则包括7个,这些原则并不是孤⽴存在的,它们相互依赖,相互补充。 开闭原则(Open Closed Principle,OCP)单⼀职责原则…...

第9篇:网络访问控制与认证机制
目录 引言 9.1 访问控制策略概述 9.2 认证机制的使用 9.3 密钥分发与证书机制 9.4 访问控制与认证在网络安全中的应用 9.5 网络访问控制与认证的挑战 9.6 总结 第9篇:网络访问控制与认证机制 引言 随着计算机网络的不断普及,安全问题日益受到关…...

CentOS安装NVIDIA驱动、CUDA以及nvidia-container-toolkit
0.提前准备 0.1.更新yum源(以阿里为例) 0.1.1 备份当前的yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 0.1.2 下载新的CentOS-Base.repo 到/etc/yum.repos.d/ CentOS 5 wget -O /etc/yum.repos.d/CentOS-Base…...

STM32调试,发现HAL_Init();之后无法调试,甚至无法让程序停下来
参考文档: STM32调试,发现HAL_Init();之后无法调试,甚至无法让程序停下来 - asml - 博客园 症状 最近开始学习STM32Cube,发现新建工程后无法正常调试,过了HAL_Init();之后就无法继续调试了. 无法进行让程序暂停以及停止等操作.并在输出窗口不断刷出 ERROR: Can n…...

Ajax(web笔记)
文章目录 1.Ajax的概念2.Ajax 的作用3.原生Ajax4.Axios4.1Axios的概念4.2Axios入门 1.Ajax的概念 AsynchronousJavaScriptAndXML,异步的JavaScript和XML 2.Ajax 的作用 数据交换:过Ajax可以给服务器发送请求,并获取服务器响应的数据。异步交互:可以在…...

多入口+vite+vue3预渲染方案
如果你的项目要求加载速度要快,我们如果使用传统的vue3+sfc模式去开发,因为只有一个根节点,空白页面加载出来之后js才回去加载组件渲染,这样页面总是有一个短暂的空白。我们这里不讨论服务器端ssr和预渲染方案,仅仅是为了满足比较极端的优化需求,在这种情况下我的这套方案…...

Vue3+Ts函数封装与应用
目录 一、基础函数 二、实际应用 2.1、根据id找到对应的value值(找第一个) 2.2、根据id找到对应的value值(找所有) 2.3、不重复的升序数组找数字(二分查找) 2.4、重复的无序数组找数字(统计个数) 2.5、将数组整理为树结构(省市区为例) 为什么要积累呢?因为面…...

C语言全局变量和局部变量同时应用的题题型[求一堆数组中10个学生的成绩里最高分、最低分和平均分。]
C语言函数 全局变量与局部结合变量题。 本片代码中包含全局变量max和min。 以及局部变量aver。 全局变量运用于从定义变量开始,局部变量运用于定义它的调用函数内。 正文开始: #include <stdio.h> int max0,min0; int main() { int average(int array[…...

深度学习实战94-基于图卷积神经网络GCN模型的搭建以及在金融领域的场景
大家好,我是微学AI,今天给大家介绍一下深度学习实战94-基于图卷积神经网络GCN模型的搭建以及在金融领域的场景。文章首先介绍了GCN模型的原理及模型结构,随后提供了数据样例,并详细展示了实战代码。通过本文,读者可以深入了解GCN模型在金融场景下的应用,同时掌握代码的具…...

.NET 6新特性 | System.Text.Json功能改进
在.NET 6.0中,JSON处理库得到了显著的改进,主要体现在System.Text.Json上。以下是对.NET 6.0中改进的JSON处理库的详细分析: 一、System.Text.Json的引入与优势 在.NET 6中,Microsoft引入了新的JSON库System.Text.Json作为官方推…...

Matlab如何对全局优化算法启动并行计算
在 MATLAB 中,启用并行计算可以显著提高一些优化算法(如遗传算法 ga 和粒子群算法 particleswarm)的速度,特别是在种群或粒子群较大时。要启用并行计算,可以使用 UseParallel 参数。 1. 启用并行计算步骤 Step 1: 检…...

MYSQL-查看数据库中的存储过程语法(六)
13.7.5.9 SHOW CREATE PROCEDURE 语句 SHOW CREATE PROCEDURE proc_name此语句是 MySQL 扩展。它返回确切的字符串 ,可用于重新创建命名的存储过程。SHOW CREATE FUNCTION,显示有关存储函数的信息 (参见第 13.7.5.8 节“ SHOW CREATE FUNCTI…...

【深度学习】(12)--模型部署 <连接客户端与服务端>
文章目录 模型部署一、模型部署的定义与目的二、模型部署的步骤三、模型部署的方式四、Flask框架五、实现模型部署1. 搭建服务端1.1 初始化Flask app1.2 加载模型1.3 数据预处理1.4 构建装饰器1.5 完整代码 2. 搭建客户端2.1 服务端网址2.2 发送请求2.3 完整代码 六、运行使用 …...

优化SQL查询的最佳实践:提升数据库性能的关键
SQL 查询是数据库操作的核心,特别是当数据量庞大时,性能问题尤为明显。优化 SQL 查询不仅能减少响应时间,还能提高系统整体的可伸缩性。本文将从索引、查询结构、数据库设计和缓存等方面详细介绍如何优化 SQL 查询以提升性能。 一、索引的使…...

【AIGC视频生成】视频扩散模型(综述+最新进展)
文章目录 一、综述1.1 扩散模型1.1.1 Denoising Diffusion Probabilistic Models (DDPMs)1.1.2 Score-Based Generative Models (SGMs)1.1.3 Stochastic Differential Equations (Score SDEs) 1.2 相关任务1.3 数据集1.4 评价指标 二、年度进展1.runway gen2.1 Gen-1࿱…...

如何下载3GPP协议?
一、进入3GPP网页 https://www.3gpp.org/ 二、点击“Specifications &Technologies” 三、点击“FTP Server” 网址: https://www.3gpp.org/specifications-technologies 四、找到“latest”,查看最新版 网址: https://www.3gpp.org/ftp…...

目标检测系统操作说明【用户使用指南】(python+pyside6界面+系统源码+可训练的数据集+也完成的训练模型)
1.100多种【基于YOLOv8/v10/v11的目标检测系统】目录(pythonpyside6界面系统源码可训练的数据集也完成的训练模型) 2.目标检测系统【环境搭建过程】(GPU版本) 3.目标检测系统【环境详细配置过程】(CPU版本࿰…...

Vue中使用路由
目录 单页应用程序:SPA - Single Page Application路由 VueRouterVueRouter使用步骤组件存放目录问题 路由模块封装声明式导航 - 导航连接两个类名自定义匹配类名 声明式导航 - 跳转传参Vue路由 - 重定向Vue路由 - 404Vue路由 - 模式设置 编程式导航 - 基本跳转编程…...