数据结构开始——时间复杂度和空间复杂度知识点笔记总结

server/2024/12/14 5:18:47/

好了,经过了漫长的时间学习c语言语法知识,现在我们到了数据结构的学习。

首先,我们得思考一下

什么是数据结构

数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。


算法又是什么?

定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果
 

算法讲究效率

如何衡量一个算法的好坏
如,斐波那契数列(虽然代码简单,但是它的效率高吗?)

long long Fib(int n)
{
if(n < 3)
{return 1;
}return Fib(n-1) + Fib(n-2);
}

是先(黑色)一步一步进去下去,算到Fib(1)时,再一步一步返回上去(红色) 

这得花费非常多的时间,效率并不高。

因此,要衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的即时间复杂度,空间复杂度

时间复杂度和空间复杂度

时间复杂度和空间复杂度的区别

作用:时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间(由于目前的空间都很大,基本不用担心空间的)

时间复杂度

时间复杂度:算法的时间复杂度是一个函数
一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度

也就是:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。


 

 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{                                  这里是N^2
++count;
}
}
for (int k = 0; k < 2 * N ; ++ k)
{
++count;                             这里是2*N
}
int M = 10;
while (M--)
{                                     这里是10次
++count;
}

经过计算我们可以得出:是F(N)=N^2+2*N+10

其实,对于求复杂度,我们一般使用大O的渐进表示法,,现在来介绍下:

大O的渐进表示法

1.大O符号:是用于描述函数渐进行为的数学符号

2.规则方法:

1)用常数1取代运行时间中的所有加法常数。

2)、在修改后的运行次数函数中,只保留最高阶项

3)、如果最高阶项存在且不是1,则去除与这个项目相乘的常数

最后得到的结果就是大O阶

eg:N(10000000000000)这应该写成什么呢?

答案:仍然是O(1).

有人会问为啥它这个数那么大了,还是用O(1)表示呢?

现在我们来用形象的例子来解释下:

假如那里有座泰山,我们要铲平,
不同视角:会不同
我们人的视角觉得它非常大,
但是放到太阳系里面,就显得非常小了。

同样,对于编程也是一样的道理。

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数

另外有些算法的时间复杂度存在最好、平均和最坏情况:

最坏情况:任意输入规模的最大运行次数(上界)平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)例如:在一个长度为N数组中搜索一个数据x

最好情况:1次找到最坏情况:N次找到平均情况:N/2次找到

所以,我们实际中一般关注的是最坏的情况。

原因:同样是以一个形象的例子来解释

约会的预期管理
情人节约会可能到时的时间
最好:17:00
平均:18:00
最坏:19:00
那么因为只有30%的机会准时到
所以是不是选择说19:00时最好,把预期拉到最低。

常见时间复杂度计算举例

void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
{                                     这里是2*N次
++count;
}
int M = 10;
while (M--)                          这里是10次
{
++count;
}
printf("%d\n", count);
}

所以,函数是不是就是:F(N)=2*N+10

根据大O法:得时间复杂度就是O(N)

例子2:

// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++ k)
{
++count;                       这里是M次
}
for (int k = 0; k < N ; ++ k)
{                                这里是N次
++count;
}
printf("%d\n", count);
}

函数式:F(N)=M+N

注意:这里的M,N都是未知数,并不是具体数

所以根据大O法:时间复杂度O(M+N)
 

例子3:

// 计算Func4的时间复杂度?
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{                                     这里是100次
++count;
}
printf("%d\n", count);
}

函数式:F(N)=100

根据大O法:由于它是常数嘛,所以用1代替

所以时间复杂度:O(1).

例子4:

const char * strchr ( const char * str, int character );

对于指针,如果没有具体说,一般默认都是O(N)

例子5:

void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end){                                        一般没说的话,默认N次int exchange = 0;for (size_t i = 1; i < end; ++i)          因为两个for循环,所以是N^2次{if (a[i-1] > a[i]){Swap(&a[i-1], &a[i]);exchange = 1;}}if (exchange == 0)              因为这里有了判断,它可以改变最好的情况break;                          如果没有这句,}                                最好也是O(N^2)因为它无法知道它已经排序了  }                                

排序

最好(已经排序好大小):O(1)  X有人说是看一眼就知道它的大小排序了,但是你一亿的时候呢,你并不知道其他数字2与这个的大小?

 你是不是也是要一个一个对比下去呢?
所以  时间复杂度: O(N)  √   

最坏:O(N^2)
其实这里不敢数循环,因为以后会学更复杂的
N-1 N-2 N-3....3 2 1  等差数列 算出O(N^2)

例子5:我们之前写过的二分查找

int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n-1;while (begin < end){int mid = begin + ((end-begin)>>1);if (a[mid] < x)begin = mid+1;else if (a[mid] > x)end = mid;elsereturn mid;
}return -1;
}

 

          四    第三次             第二次                     第一次 

 我们可以发现,这是不是一次查找,我们就可以筛选掉一半。

最坏情况:区间缩放到一个值时,要么找到,要么找不到
假设N是数组个数,x是最坏查找次数
N/2/2/2/2.../2=1
折半查找多少次就除多少个2
假设是x次

2^x=N
x=log N 

这里我们先来说明一下:

接着,我们来看一下二分查找的显著优势:

对比:

N         1000    100W    10亿                  2^10=1024

O(N)      1000    100W   10亿                 2^20=1024*1024

O(log N)  10       20         30                     2^30=1024*1024*1024  

我们可以看到这二分查找极大改变它的时间效率。

但是一个很不友好的一点是,它仅仅局限于排序好的数,所以我们以后也不太实用

 例子6:

 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}

 这是不是相当于有N+1次,O(N+1)

根据大O法:时间复杂度:O(N)

Fac(N)
Fac(N-1)
Fac(N-2)
....
Fac(1)    
Fac(0)       

例子七:

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}

 等比数列 -缺失(常数)

时间:O(2^N)
空间:O(N)

对此,我们可以得出来:

时间一去不复返,不可重复利用
空间用了以后要归还,可以重复利用。

空间复杂度

1.空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度
2.空间复杂度算的是变量的个数(而不是程序占用了多少bytes的空间,因为这个也没太大意义)

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显示申请的额外空间来确定。

3.空间复杂度的使用规则也是:大O法

空间复杂度举例:

例子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;} 
}

我们上面说了空间复杂度算的是变量的个数

我们看到了上面用了三个变量:end,exchange,i。 -->O(3)

根据大O法:O(1).

例子2:绝大多数空间复杂度都是O(1)或O(N)

 计算Fibonacci的空间复杂度?返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{if(n==0)return NULL;                       long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));fibArray[0] = 0;                        为什么要多加1,因为数组从0开始,fibArray[1] = 1; for (int i = 2; i <= n ; ++i){fibArray[i] = fibArray[i - 1] + fibArray [i - 2];}return fibArray;
}

上面创建了n+1个空间

所以根据大O法:O(N)

例子3:

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
if(N == 0)
return 1;
return Fac(N-1)*N;
}

 值得注意的是

递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间

函数在栈的开辟函数栈帧时是单独开辟的。调用本身时,会再2开辟一块空间作为新的函数栈帧,但是在此外并没有创建额外的变量,所以会认为创建的每一块栈帧空间复杂度变量是常数个。

举个形象例子:

空间销毁是把那个使用权还给操作系统了,并不是说这块空间就没了
内存的申请,就相当于 住酒店一样,当这房间不用了,这不是说把它炸

所以参数从N到0,共递归了N+1次,

根据大O法:O(N)

例子6:

同样,斐波那契数列也是一样。

long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}

 空间销毁是把那个使用权还给操作系统了,我们上面说过空间是可以重复使用的,

调用某个函数结束后,再次调用此函数,所用的空间与上一次的函数空间是同一个。

所以,同样是递归了N-1次

由大O法:O(N)。

大O的常见的变化表

祝福语:

最后的最后,希望我们都能战胜困难,一步一步地坚持下去,为以后拿到好offer!


http://www.ppmy.cn/server/150012.html

相关文章

MySQL|通过JSON_UNQUOTE实现MySQL中JSON数据的干净提取

文章目录 语法使用示例注意事项 JSON_UNQUOTE() 是 MySQL 中用于处理 JSON 数据类型的一个函数。它的主要作用是从 JSON 字符串中移除最外层的引号&#xff0c;这对于从 JSON 对象或数组中提取字符串值特别有用。 语法 JSON_UNQUOTE(json_string)json_string: 这是你想要去掉引…

笔记本电脑升级硬盘存储、Windows10系统安装及后续步骤(以联想ThinkPad X1 Carbon Gen10为例)

文章目录 1.前言2.材料准备3.Win10系统安装盘制作3.1 系统下载3.2 系统启动U盘刻录 4.拆机更换硬盘5.开机启动项修改6.系统安装&#xff08;以Win10为例&#xff09;7.系统安装后可能需要的步骤7.1 缺少WIFI等网络驱动7.2 系统激活7.3 办公软件安装 8.旧硬盘变废为宝参考文献 1…

短视频矩阵抖音SEO源码OEM独立部署

短视频优化矩阵源码涉及对抖音平台上的视频内容进行筛选与排序&#xff0c;目的是增强其在搜索引擎中的可见度&#xff0c;以便更多用户能够浏览到这些视频。而抖音SEO优化系统则是通过构建一个分析框架&#xff0c;来解析抖音上的用户数据、视频信息及标签等元素&#xff0c;并…

手机租赁系统开发全面解析与实现指南

内容概要 手机租赁系统的设计理念是为了满足用户对便捷、灵活的手机使用需求。想象一下&#xff0c;谁还愿意花大价钱买一部手机呢&#xff1f;尤其是当新款手机频繁推出时&#xff0c;租赁似乎成了更受欢迎的选择。这个系统旨在让用户可以随时随地选择租用不同型号的手机&…

docker安装Emqx并使用自签名证书开启 SSL/TLS 连接

docker安装Emqx并使用自签名证书开启 SSL/TLS 连接 一、获取自签名证书1、创建openssl.cnf文件2、生成证书自签名证书 二、docker安装EMQX1、初始化目录2、加载镜像文件并挂载相应的文件目录3、启动docker容器4、EMQX加载自签名证书 三、客户端MQTTX连接测试四、Springboot整合…

远程协助,打工人的数字生活私人医生

在数字化浪潮的席卷下&#xff0c;电子设备和信息技术已经深入我们生活的方方面面。然而&#xff0c;随之而来的技术难题和电子设备的故障也常常让我们头疼不已。如何快速、简便地解决这些问题&#xff0c;成为了公众关注的热点。这时&#xff0c;远程协助就像是我们生活中不可…

408数据结构:树与二叉树选择题做题笔记

408数据结构 第一章 绪论 第二章 线性表 绪论、线性表选择题做题笔记 第三章 栈、队列和数组 栈、队列和数组选择题做题笔记 第四章 串 第五章 树与二叉树 树与二叉树选择题做题笔记 文章目录 408数据结构第一节 树的基本概念&#xff08;1&#xff09;知识点补充&#xff08;…

【JS】js处理跨域的方案之一:jsonp 详解

文章目录 1. 引言2. 同源策略和跨域3. html 存在的特殊情况4. JSONP5. JSONP优缺点参考链接 1. 引言 在实际开发中&#xff0c;数据都是后端返回的&#xff0c;那就需要前端调用后端的接口&#xff0c;来拿到数据 前端中调接口的方式一般有如下三种 Ajaxfecthaxios 这个是最…