数据结构初步(一)- 时间与空间复杂度

news/2024/5/18 13:23:57/文章来源:https://blog.csdn.net/weixin_64904163/article/details/126643022

目录

  • 前言
  • 1. 数据结构与算法
    • 1.1 数据结构是啥
    • 1.2 算法是啥
  • 2. 算法效率
    • 2.1 如何衡量一个算法的效率
    • 2.2 算法的复杂度
  • 3. 时间复杂度
    • 3.1 概念
    • 3.2 大O的渐进表示法
    • 3.3 例子分析
      • 计算Func2的时间复杂度
      • 计算Func3的时间复杂度
      • 计算Func4的时间复杂度
      • 计算strchr的时间复杂度
      • 计算冒泡排序的时间复杂度
      • 计算二分查找的时间复杂度
      • 计算阶乘递归的时间复杂度
      • 计算斐波那契数列递归形式的时间复杂度
  • 4. 空间复杂度
    • 4.1 概念
    • 4.2 大O的渐进表示法
    • 4.3 例子分析
      • 计算冒泡排序的空间复杂度
      • 计算斐波那契数列的空间复杂度
      • 计算阶乘递归的空间复杂度
  • 常见复杂度对比
  • 结语

前言

当当当,本节开始进入到数据结构的学习之旅。什么是数据结构呢,什么又是时间复杂度与空间复杂度呢?学习数据结构的道路并不是一帆风顺的,唯有持续冲锋数据结构的高地。


1. 数据结构与算法

1.1 数据结构是啥

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

1.2 算法是啥

算法algorithm是一系列的计算步骤,用来将输入数据转换为输出结果。
数据结构与算法是相辅相成的,二者往往同时出现。


2. 算法效率

2.1 如何衡量一个算法的效率

如果给出一个算法,我们如何知道这个算法的效率是怎样的呢?一个程序的源代码简洁,对应的算法效率也会高吗?相同的算法代码在不同机器上的具体执行时间也会有这差别,这取决于机器的新旧。
为了只关注算法本身的效率,而忽略具体环境对算法程序运行造成的影响,前人提出了著名的复杂度方法大O的渐进表示法去衡量一个算法的效率。

2.2 算法的复杂度

算法在编写成可执行程序后,运行时需要耗费时间资源和空间资源也就是内存
衡量一个算法的效率高低,一般会从时间和空间两个维度出发,引出了时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,空间复杂度主要衡量一个算法运行时所需要的额外空间。在计算机发展的早期,计算机的容量很小,所以对空间复杂度特别重视,而不那么重视时间复杂度。但是经过计算机行业的迅猛发展,计算机的储存容量有了显著的提升,如今一个算法的空间复杂度不在那么收到人们的关注,而是逐渐重视算法的时间复杂度,毕竟时间就是生命。

3. 时间复杂度

3.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){++count;}}for (int k = 0; k < 2 * N ; ++ k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}

count共执行了N2N^2N2次,即函数F(N)=N2+2∗N+10F(N)=N^2+2*N+10F(N)=N2+2N+10
实际计算时间复杂度时,我们不需要计算精确的执行次数,只需要大概执行次数,也就是大O的渐进表示法

3.2 大O的渐进表示法

大O符号Big O notation:用于描述函数渐进行为的数学符号。
推导大O阶方法:

  1. 用常数1取代运行时间中的所有加法常数;
  2. 在修改后的运行次数函数中只保留最高阶项;
  3. 如果最高阶项存在且最高阶项系数不是1,则去掉与最高阶项相乘的常数系数。
  4. 得到大O阶

对于一个与执行次数相关的函数F(N)=N2+2∗N+10F(N)=N^2+2*N+10F(N)=N2+2N+10使用大O阶渐进表示后为O(N2)O(N^2)O(N2)
为什么我们可以这样去掉函数的一部分项呢?
大O阶表示其实去掉了对函数结果影响不大的项,简洁明了的表示了执行次数。
例子如下表所示:
F(N)=N2+2∗N+10F(N)=N^2+2*N+10F(N)=N2+2N+10

N的取值N^2+2*N+10 执行次数N^2执行次数
1131
10130100
1001021010000
100010020101000000

随着N取值的增大,函数实际的执行次数与大O渐进表示后的执行次数逐渐趋于接近,最高项有着决定性的作用(如果有最高项的话)。

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

最坏情况:任意输入规模的最大运行次数上届
平均情况:任意输入规模的期望输入次数;
最好情况:任意输入规模的最小运行次数下届

例子:对于在长度为N的数组中顺序查找某个数据x
最坏情况:N次找到或找不到;
平均情况:N/2找到;
最好情况:1次找到。

在实际情况中一般关注的是算法的最坏运行情况,看的是最坏时间复杂度。本例中为O(N)O(N)O(N)


3.3 例子分析

通过具体的例子来进一步了解时间复杂度。
简单的算法程序可以通过观察代码直接得到时间复杂度;
复杂的算法程序如果我们仅仅通过观察代码得到结果,那么这个结果很可能就是错误的。我们需要逐步的画图分析算法的具体实现步骤,在通过大O阶推导,以此来得到算法的时间复杂度。

计算Func2的时间复杂度

// 计算Func2的时间复杂度
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);
}

执行实际次数的函数F(N)=2∗N+10F(N)=2*N+10F(N)=2N+10
大O阶渐进表示时间复杂度:O(N)O(N)O(N)


计算Func3的时间复杂度

// 计算Func3的时间复杂度
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);
}

执行实际次数的函数F(N)=M+NF(N)=M+NF(N)=M+N
大O阶推导时间复杂度O(M+N)O(M+N)O(M+N)


计算Func4的时间复杂度

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

执行实际次数的函数F(N)=100F(N)=100F(N)=100
大O阶推导时间复杂度O(1)O(1)O(1)


计算strchr的时间复杂度

// 计算strchr的时间复杂度
const char * strchr ( const char * str, int character );

strchr()函数功能:在一个字符串中顺序查找指定的一个字符并返回这个字符的地址或空指针。
假设该字符串长度时N。
image.png

执行实际次数有多种情况:最好1次找到;平均N/2次找到;最坏N次找到或直接找不到。
大O阶推导的时间复杂度(最坏)O(N)O(N)O(N)


计算冒泡排序的时间复杂度

// 计算BubbleSort的时间复杂度
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;}
}

冒泡排序思想,对于一组有N个元素的数据,从头开始依次比较相邻的两个数据,满足条件大于或小于时交换相邻的这两个数,这样一趟下来最后一个元素第N个元素就是所有元素中的最大的或者最小的。相邻两个元素比较操作进行了N次。
下一趟只需要比较前N-1个元素,者一趟下来本趟中最后一个元素第N-1个元素就是待比较元素中最大的或最小的。相邻两个元素比较操作进行了N-1次。
可以知道最后一趟比较操作了1次。
那么冒泡排序操作共进行了N趟,每一趟比较操作进行了从N开始的递减次数。
即比较操作共进行了N+(N−1)+...+1=(N∗(N+1))/2N+(N-1)+...+1=(N*(N+1))/2N+(N1)+...+1=(N(N+1))/2
4$5G[5`%I76(PK]}PEF)9S.gif

最坏执行次数(逆序状态)(N∗(N−1))/2(N*(N-1))/2(N(N1))/2
最好执行次数(已经是排好序的状态)NNN
大O阶推导时间复杂度O(N2)O(N^2)O(N2)


计算二分查找的时间复杂度

// 计算BinarySearch的时间复杂度
int BinarySearch(int* a, int n, int x){assert(a);int begin = 0;int end = n-1;// [begin, end]:begin和end是左闭右闭区间,因此有=号while (begin <= end){int mid = begin + ((end-begin)>>1);if (a[mid] < x)begin = mid+1;else if (a[mid] > x)end = mid-1;elsereturn mid;}return -1;
}

对于N个有序排列的元素的一组数据,查找其中的某个元素x不需要从第一个元素开始寻找,因为这是有序的数据,有着不同于乱序数据的方式。
首先查找有序元素的中间位置的数据,如果中间位置的元素就是要找的元素x就不在继续;否则就继续查找,因为数据是有序的,那么比较中间位置元素与待查找元素x的大小就可以减少一半需要查找的元素:如果中间位置元素大于x,那么就在中间位置左边部分继续查找;如果中间位置元素小于x,那么就在中间位置右边部分继续查找。
每一次查找都在原来基础上缩小一半的查找范围,直到只剩下最后一个元素,此时要么最后一个元素就是要找的x,要么就找不到x。那么总共查找了几次呢?
假设查找了F次,N/2/2.../2=1N/2/2.../2=1N/2/2.../2=12F=N2^F=N2F=N可以得到F=log2NF=log_2NF=log2N

最好执行次数:1次
最坏执行次数:log2Nlog_2Nlog2N
大O阶推导时间复杂度:O(log2N)O(log_2N)O(log2N)O(logN)O(logN)O(logN)
因为对数的底数不好在电脑上打出来,一般简写略去底数,一般O(log2N)O(log_2N)O(log2N)表示O(logN)O(logN)O(logN)


计算阶乘递归的时间复杂度

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

函数调用涉及函数栈帧的创建与销毁过程。
输入一个无符号整数N,第一次调用函数fac(N),创建fac(N)的函数栈帧;第二次在fac(N)内部调用函数fac(N-1),创建fac(N-1)的函数栈帧;......... 最后第N+1次在函数fac(1)内部调用fac(0),创建fac(0)的函数栈帧。随后从fac(0)开始返回,相应的函数栈帧逐步销毁。
基本操作次数(执行次数)N+1N+1N+1
大O阶推导时间复杂度O(N)O(N)O(N)
image.png


计算斐波那契数列递归形式的时间复杂度

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

image.png

函数调用次数共计:20+21+22+...+2N−12^0+2^1+2^2+...+2^{N-1}20+21+22+...+2N1 =2N−12^N-12N1
大O阶推导时间复杂度O(2N)O(2^N)O(2N)


4. 空间复杂度

4.1 概念

前面稍微了解了时间复杂度的概念,空间复杂度与时间复杂度类似。

空间复杂度是一个数学函数,对一个算法在运行过程中临时占用储存空间大小的量度。

如同时间复杂度不是计算算法程序运行的具体时间,空间复杂度表示的也不是计算算法程序具体占用了多少字节的空间,而是计算的是算法程序的变量个数**额外空间**

时间复杂度与空间复杂度有着不同,其中最明显的一个特点是:同一时间不能被程序重复利用,同一块空间能够被程序重复利用。在计算复杂度时需要注意这一个点。

4.2 大O的渐进表示法

大O符号Big O notation:用于描述函数渐进行为的数学符号。
推导大O阶方法:

  1. 用常数1取代运行时间中的所有加法常数;
  2. 在修改后的运行次数函数中只保留最高阶项;
  3. 如果最高阶项存在且最高阶项系数不是1,则去掉与最高阶项相乘的常数系数。
  4. 得到大O阶

与时间复杂度的表示形式类似。

4.3 例子分析

计算冒泡排序的空间复杂度

// 计算BubbleSort的时间复杂度
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;}
}

程序开辟了3个额外空间:整型变量end、exchange、i,是常数个额外空间。
大O阶推导空间复杂度O(1)O(1)O(1)


计算斐波那契数列的空间复杂度

// 计算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;fibArray[1] = 1;for (int i = 2; i <= n ; ++i){fibArray[i] = fibArray[i - 1] + fibArray [i - 2];}return fibArray;}

斐波那契数列:1,1,2,3,5,8,13,21,...1,1,2,3,5,8,13,21,...1,1,2,3,5,8,13,21...
程序开辟了1long long*类型的栈区空间fibArrayn+1long long类型的堆区空间、1int类型的栈区空间,即n+3个额外空间。

大O阶推导时间复杂度O(N)O(N)O(N)


计算阶乘递归的空间复杂度

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

每一次递归函数fac()开辟的空间是常数个O(1)O(1)O(1),共递归调用开辟函数栈帧N+1
则空间复杂度就是O(N)O(N)O(N)


常见复杂度对比

常数阶O(1)O(1)O(1)
线性阶O(n)O(n)O(n)
平方阶O(n2)O(n^2)O(n2)
对数阶O(log2n)O(log_2n)O(log2n)O(logn)O(logn)O(logn)
n∗lognn*lognnlognO(nlogn)O(nlogn)O(nlogn)
立方阶O(n3)O(n^3)O(n3)
指数阶O(2n)O(2^n)O(2n)

时间复杂度或空间复杂度随元素数量N增加变化的曲线图
image.png


结语

本节主要介绍了数据结构入门的概念,着重介绍了时间复杂度与空间内复杂度,我们需要掌握计算一个算法时间复杂度与空间复杂度的方法。现在我们已经踏入了数据结构的大门,开始在数据结构与算法的世界里闯荡!


ENDENDEND

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_4396.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

端口号被占用解决办法(超详细)

文章目录问题描述java.net.BindException: Address already in use: JVM_BindWeb server failed to start. Port 8899 was already in use.解决方案问题描述 java.net.BindException: Address already in use: JVM_Bind Web server failed to start. Port 8899 was already in…

极几何,本质矩阵,基础矩阵,单应矩阵

什么是三角化&#xff1f; 三角化就是下图的红字部分&#xff1a; 什么是极几何&#xff1f; 极几何描述了同一场景或者物体在两个视点图像间的对应关系。 下图中的O1和O2分别是两个相机的光心&#xff0c;即摄像机坐标系的原点。由下图可知给定了一个三维空间下的P点&…

07-Linux基本权限

1. 权限基本概述 1.1 什么是权限&#xff1f; 权限: 操作系统对用户能够执行的功能所设立的限制, 主要用于约束用户能对系统所做的操作, 以及内容访问的范围, 或者说, 权限是指某个特定的用户具有特定的系统资源使用权力.1.2 为什么要有权限&#xff1f; 因为系统中不可能只…

最详解消息队列以及RabbbitMQ之HelloWorld

1、消息队列 1、MQ的相关概念 1、什么是MQ MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是message 而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。 在互联…

webpack中的插件

1.webpack插件的作用通过安装和配置第三方插件,可以拓展webpack的能力,从而让webpack用起来更方便。最常用的webpack插件如下有两个:webpack-dev-server 类似于node.js阶段用到的nodemon工具 每当修改了源代码,webpack会自动进行项目的打包和构建html-webpack-pluginwebpac…

(分布式缓存)Redis哨兵

对应的教程视频&#xff1a; 高级篇Day3-03-Redis哨兵_哔哩哔哩_bilibili 目录&#xff1a; 哨兵的作用和原理搭建哨兵集群RedisTemplate的哨兵模式 一、哨兵的作用和原理 二、搭建哨兵集群 1.集群结构 这里我们搭建一个三节点形成的Sentinel集群&#xff0c;来监管之前的Re…

C++版本的OpenCV 5.x编译生成opencv-python==5.x(GPU版本)接口并进行调用

实现文章连接&#xff1a;强力推荐】基于Nvidia-Docker-Linux(Ubuntu18.04)平台&#xff1a;新版OpenCV5.x(C)联合CUDA11.1(GPU)完美配置视觉算法开发环境 目录1、关于有粉丝私信问我怎么调用的问题2、opencv5.x&#xff08;GPU&#xff09;测试成功opencv-python5.x测试代码Op…

黑马C++ 02 核心6 —— 类和对象_继承(重难点)

文章目录1.1 继承基本语法普通实现(重复率高)继承实现(减少重复代码)1.2 继承方式公共继承保护继承私有继承1.3 继承中的对象模型1.4 继承中构造与析构顺序1.5 继承同名成员处理方法同名成员属性同名成员函数1.6 继承同名静态成员处理方式1.6.1 同名静态成员属性通过对象访问通…

第9章 Spring的数据库编程

目录/Contents第9章 Spring的数据库编程学习目标学习内容1 Spring JDBC1.1 JDBCTemplate概述1.1.1 JDBCTemplate作用1.1.2 抽象类JdbcAccessor的属性1.2 Spring JDBC的配置1.2.1 Spring JDBC中的4个包说明1.2.2 dataSource配置4个属性的含义1.2.3 dataSource属性值的设定要求2 …

【中秋怎么过】许一个愿,希望成都不要在静默管理中过中秋

今年的中秋又要到啦&#xff0c;诚邀亲爱的博主参与投稿&#xff0c;分享“程序员”视角下的中秋夜之美&#xff01; 内容可以是&#xff1a; 程序员过中秋的正确方式&#xff1a;团圆、赏月、还是惨兮兮地加班&#xff1f;互联网大厂的中秋仪式感&#xff1a;壕无人性&#…

嵌入式Linux入门-Linux文件IO讲解并实现copy程序

嵌入式Linux入门学习教程汇总&#xff1a;嵌入式Linux教程—裸机、应用、驱动完整教程目录 在Linux系统中&#xff0c;一切都是“文件”&#xff1a;普通文件、驱动程序、网络通信等等。所有的操作&#xff0c;都是通过“文件IO”来操作的。 IO就是input和output&#xff0c;…

[PostgreSQL的 SPI_接口函数]

Server Programming Interface&#xff08;SPI&#xff09;是PostgreSQL内核中的一个模块&#xff0c;这个模块让内核开发者可以在C函数中执行SQL语句&#xff0c;并具备管理事务的能力。通过它我们可以用C语言去调用数据库里的各种SQL。 这个SPI_比较便利的一点在于&#xff…

如何使用 Bootstrap 处理 CSS

如何使用 Bootstrap 处理 CSS 大家好!如果您像我一样开始使用 CSS 编码并使用它进行任何大型项目,那么您肯定会因为响应式布局、溢出和选择器特异性而感到数不清的头痛。这就是几周前我决定学习 Bootstrap 的原因,这里是它的文档和主要功能的简短描述,所以你也可以。引导程…

一、Azkaban简明笔记

1、azkaban部署 主要是集群部署安装。 1.1 准备安装包Downloads (azkaban.github.io)1.2 配置MySQL启动mysql mysql -uroot -proot创建azkaban数据库 create database azkaban;创建azkaban用户并赋予权限(可以不设置账号,继续使用root账号) -- 显示相关变量 SHOW VARIABLES …

实体店主最爱的中秋活动方案,直接照搬就能轻松爆单!

中秋将至&#xff0c;传统佳节和营销好时机一起到了&#xff01;利用氛围浓厚的节日进行营销活动&#xff0c;是吸引客户、增粉卖货的最佳手段之一&#xff0c;商户老板们可千万不能错过&#xff01; 但是&#xff0c;活动人人都能做&#xff0c;如何在一片节日促销中脱颖而出&…

JVM详解

1. 源文件 源文件就是我们编写Java代码的文件。文件扩展名为.java 2. 字节码文件 字节码文件是源文件编译后的文件。字节码文件是二进制文件&#xff0c;需要通过特定的工具才能查看。里面存放了源文件编译后的字节码指令。 3. 类加载器 Class Loader Java 程序运行时会由…

USB转GPIO应用方案

概述 沁恒提供的多款USB转接系列芯片均提供GPIO引脚功能&#xff0c;各引脚支持独立的输出输入&#xff0c;GPIO功能的使用需要与计算机端厂商驱动程序和应用软件配合使用。各芯片的默认GPIO引脚状态有所区别&#xff0c;可查阅芯片技术手册或参考方案中附表。 型号 CH344Q …

基于神经网络的图像识别,人工神经网络图像识别

如何通过人工神经网络实现图像识别 。 人工神经网络&#xff08;ArtificialNeuralNetworks&#xff09;&#xff08;简称ANN&#xff09;系统从20世纪40年代末诞生至今仅短短半个多世纪&#xff0c;但由于他具有信息的分布存储、并行处理以及自学习能力等优点&#xff0c;已经…

Jmeter(五) - 从入门到精通 - 测试计划(Test Plan)的元件(详解教程)

一.测试计划&#xff08;Test Plan&#xff09;要素 1.JMeter中一个脚本就是一个测试计划&#xff08;Test Plan&#xff09;&#xff0c;也是一个管理单元。JMeter 的请求模拟与并发数(设置线程数&#xff0c;一个线程代表一个虚拟用户)设置都在脚本文件中一起设置。JMeter 不…

雨夜赶长路,房企必经的三场“价值战事”

今年上半年&#xff0c;地产行业一直在高压下运行。市场周期震荡叠加疫情等因素&#xff0c;为房企的销售、土拍、融资带来不确定性。 下半年以来&#xff0c;虽然不确定性和高压仍在&#xff0c;但市场有望恢复&#xff0c;下行趋势似乎已到拐点。 面对高压&#xff0c;不同…