[数据结构]1.时间复杂度和空间复杂度

news/2025/4/1 9:17:43/

这里写目录标题

  • 1. 算法复杂度
  • 2. 时间复杂度
    • 2.1 执行次数
    • 2.2 大O渐进表示法
    • 2.3 常见时间复杂度计算
      • eg1
      • eg2
      • eg3
      • eg4
      • eg5
      • eg6
      • eg7
      • eg8
      • eg9
  • 3. 空间复杂度
      • eg1
      • eg2
      • eg3
      • eg4
  • 4. 常见复杂度对比
  • 5. 复杂度练习
    • eg1

1. 算法复杂度

衡量一个算法的好坏,一般是从时间空间两个维度来衡量,即时间复杂度和空间复杂度。
时间复杂度衡量算法的运行快慢,空间复杂度衡量算法运行所需要的额外空间。

2. 时间复杂度

算法的时间复杂度是一个函数,定量描述了该算法的运行时间。
算法花费的时间与其中语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度。

2.1 执行次数

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);
}

Func1 执行的基本操作次数 :F(N)=N^2+2*N+10
实际计算时间复杂度时,不一定要计算精确,只需要大概执行次数,使用大O的渐进表示法O(N^2)

2.2 大O渐进表示法

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

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

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

  1. 最坏情况:任意输入规模的最大运行次数(上界)
  2. 平均情况:任意输入规模的期望运行次数
  3. 最好情况:任意输入规模的最小运行次数(下界)

2.3 常见时间复杂度计算

eg1

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)=2N+10 --> O(N)

eg2

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+N --> O(M+N)

eg3

void Func4(int N){int count = 0;for (int k = 0; k < 100; ++ k){++count;}printf("%d\n", count);
}

F(N)=100 --> O(1)

eg4

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

strchr函数用于在给定字符串str中查找字符character首次出现的位置。如果找到,返回指向该字符的指针;如果未找到,返回NULL
最好情况:1;最坏情况:O(N)

eg5

// 计算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;}
}

最好情况:O(N);最坏情况:O(N^2)

eg6

// 计算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);//((end - begin) >> 1) 相当于 (end - begin) / 2//这样写可以避免 (begin + end) / 2 可能导致的溢出问题(当 begin 和 end 很大时)if (a[mid] < x)//如果中间位置的值 a[mid] 小于要查找的值 x,说明 x 在 mid 的右侧,所以将 begin 更新为 mid + 1begin = mid + 1;else if (a[mid] > x)//如果中间位置的值 a[mid] 大于要查找的值 x,说明 x 在 mid 的左侧,所以将 end 更新为 mid - 1end = mid - 1;else//如果 a[mid] 等于 x,说明找到了目标值,直接返回 mid,即目标值在数组中的下标return mid;}return -1;
}

eg7

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

对于输入为N时,函数会进行N次递归调用,每次递归调用除了递归自身外其他操作时间近似为常数,所以整体时间复杂度与输入规模N成线性关系,即O(N)

eg8

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

每次递归调用都会产生两个新的递归调用,随着N的增大,计算量呈指数增长。
计算Fib(N)时,需要计算Fib(N - 1)Fib(N - 2),而计算Fib(N - 1)又需要计算Fib(N - 2)Fib(N - 3)等等,存在大量的重复计算,导致计算量迅速膨胀。O(2^N)

eg9

消失的数字
数组nums包含从0n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?
示例:

  • 输入:[9,6,4,2,3,5,7,0,1]
  • 输出:8

分析:

  1. 先排序,再查找,n的下一个数字不是n+1,则n+1为缺失数字
    N*logN+N -->O(N*logN)
  2. 异或 (同0异1)(aba=b)
    O(2N)–>O(N)
int missingNumber(int* nums, int numsSize) {int x = 0;for (int i = 0; i < numsSize; ++i) {x ^= nums[i];}for (int i = 0; i < numsSize + 1; ++i) {x ^= i;}return x;
}
  1. 用0~N的等差数列公式计算数组之和,减数组中的值
    O(N)
int missingNumber(int* nums, int numsSize) {int x = (1 + numsSize) * numsSize / 2;for (size_t i = 0; i < numsSize; ++i) {x -= nums[i];}return x;
}

3. 空间复杂度

空间复杂度是一个数学表达式,对算法在运行过程中临时占用额外存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,算的是变量的个数,使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

eg1

// 计算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;}
}

空间复杂度通常从代码中使用的额外辅助空间来分析,这段代码主要使用了几个局部变量,空间复杂度为 O (1) ,因为除了输入数组本身外,额外使用的空间不随输入规模增长

eg2

// 计算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;
}

空间复杂度是 O (n),因为它分配了一个大小为n + 1的数组来存储斐波那契数列的前n项,空间使用量与输入的n成正比。

eg3

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

每次递归调用都会在栈上创建一个新的栈帧。在最坏情况下,会递归 N 次,所以空间复杂度是 O(N)。因为递归调用栈的深度最大为 N,需要 O(N) 的栈空间来存储每一层递归调用的状态。

eg4

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

O(N),深入建立栈帧,函数结束销毁栈帧,再建立栈帧还是在该空间建立,所以一共建立N个栈帧,Fib(N-1) + Fib(N-2);使用的是同一块栈帧

4. 常见复杂度对比

表达式大 O 表示法时间复杂度类别
12345O(1)常数阶
3n + 4O(n)线性阶
3n² + 4n + 5O(n²)平方阶
3log(2)n + 4O(logn)对数阶
2n + 3nlog(2)n + 14O(nlogn)nlogn 阶
n³ + 2n² + 4n + 6O(n³)立方阶
2ⁿO(2ⁿ)指数阶

请添加图片描述

5. 复杂度练习

eg1

轮转数组
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
输入: nums = [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]

  1. 暴力求解,旋转k次
    时间复杂度:k=n-1,kn–> O(N^2)
    空间复杂度:O(1)
  2. 三段逆置
    4 3 2 1 5 6 7 前n-k逆置
    4 3 2 1 7 6 5 后k 逆置
    5 6 7 1 2 3 4 整体逆置
    时间复杂度:O(N)
    空间复杂度:O(1)
void reverse(int* a, int left, int right) {while (left < right) {int tmp = a[left];a[left] = a[right];a[right] = tmp;++left;--right;}
}
void rotate(int* nums, int numsSize, int k) {if (k > numsSize)k %= numsSize;reverse(nums, 0, numsSize - k - 1);reverse(nums, numsSize - k, numsSize - 1);reverse(nums, 0, numsSize - 1);
}
  1. 空间换时间
    将前后拷贝至tmp,再拷贝到a
    a=[1 2 3 4 5 6 7]
    tmp=[5 6 7 1 2 3 4]
    时间复杂度:O(N)
    空间复杂度:O(N)
void rotate(int* nums, int numsSize, int k) {if (k > numsSize)k %= numsSize;int* tmp = (int*)malloc(sizeof(int) * numsSize);memcpy(tmp + k, nums, sizeof(int) * (numsSize - k));memcpy(tmp, nums + numsSize - k, sizeof(int) * (k));memcpy(nums, tmp, sizeof(int) * (numsSize));free(tmp);
}

http://www.ppmy.cn/news/1584383.html

相关文章

SpringBoot第一节

Spring Boot Spring Boot 概述 快速构建Spring的方式&#xff0c;是Spring的顶级项目之一 Spring的缺点 配置繁琐 依赖繁琐 Spring Boot功能 自动配置 自动配置是一个运行时&#xff08;应运程序启动时&#xff09;的过程&#xff0c;考虑了众多因素&#xff0c;才决定S…

如何在 Postman 中配置并发送 JSON 格式的 POST 请求?

在接口开发过程中&#xff0c;我们有常常使用 JSON 格式来传输接口数据。那如何用 Postman 通过 JSON 来发送 POST 数据呢&#xff1f;本文下面来具体介绍下&#xff5e; 使用 Postman 发送 JSON 格式的 POST 请求

git 操作记录

1、检查子模块是否在主分支上 [git submodule foreach --recursive \\n [ "$(git rev-parse HEAD)" "$(git rev-parse origin/master)" ] \\n && echo " 一致: $name" || echo "不一致: $name"] 使用 git submodule fore…

激光线检测算法的FPGA实现

激光线检测算法的FPGA实现 1. 常见的激光线检测算法 激光线检测中常用的三种算法 MAX&#xff08;最大值法&#xff09;、THRESH&#xff08;阈值法&#xff09;、COG&#xff08;灰度重心法&#xff09; 分别具有以下特点和工作原理&#xff1a; 1.1 MAX&#xff08;最大值法…

NLP高频面试题(十六)——deepspeed原理

近年来&#xff0c;随着深度学习模型尤其是大型语言模型&#xff08;LLM&#xff09;的迅猛发展&#xff0c;训练所需的资源与计算能力不断攀升。单个GPU或节点的资源已很难满足数百亿甚至上万亿参数模型的训练需求&#xff0c;这种情况下&#xff0c;多卡甚至多节点分布式训练…

5.Excel:从网上获取数据

一 用 Excel 数据选项卡获取数据的方法 连接。 二 要求获取实时数据 每1分钟自动更新数据。 A股市场_同花顺行情中心_同花顺财经网 用上面方法将数据加载进工作表中。 在表格内任意区域右键&#xff0c;刷新。 自动刷新&#xff1a; 三 缺点 Excel 只能爬取网页上表格类型的…

【汽车传感系统架构:借助传感获取安全】

为了将车辆自动化提升到一个新的水平&#xff0c;设计人员研究了 LiDAR 等传感器选项的权衡&#xff0c;并着眼于传感系统架构。 本文引用地址&#xff1a;https://www.eepw.com.cn/article/202503/468584.htm 每年&#xff0c;约有 120 万人死于道路交通事故&#xff0c;还有…

科软25机试

题目: 2025科软复试上机题&#xff08;回忆版&#xff09;题解_哔哩哔哩_bilibili 1. 字符串反转 #include<bits/stdc.h> using namespace std;void solve(string& a, int CurN) {if (!(CurN % 2)) {int right a.size() - 1;int left 0;while (left < right)…