【数据结构】6. 时间与空间复杂度

news2025/6/8 10:25:48

文章目录

  • 一、算法效率
    • 1、算法的复杂度
  • 二、时间复杂度
    • 1、时间复杂度的概念
    • 2、大O的渐进表示法
    • 3、常见时间复杂度计算
      • 1)实例1
      • 2)实例2
      • 3)实例3
      • 4)实例4
      • 5)实例5
      • 6)实例6
      • 7)实例7
      • 8)实例8
  • 三、空间复杂度
    • 1、空间复杂度的概念
    • 2、常见空间复杂度计算
  • 四、常见复杂度对比
  • 五、时间复杂度的oj练习
    • 1、消失的数字
    • 2、轮转数组

一、算法效率

如何衡量一个算法的好坏呢?比如对于以下斐波那契数列:

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

斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?

1、算法的复杂度

算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度

时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

二、时间复杂度

1、时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的执行次数,为算法的时间复杂度

// 请计算一下Func1中++count语句总共执行了多少次?
void Fun1(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)=N2+2*N+10。

  • N = 10、F(N) = 130
  • N = 100 、F(N) = 10210
  • N = 1000 、F(N) = 1002010

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法

2、大O的渐进表示法

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

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

使用大O的渐进表示法以后,Func1的时间复杂度为:O(N2)

  • N = 10 、F(N) = 100
  • N = 100 、 F(N) = 10000
  • N = 1000 、F(N) = 1000000

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

另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)。
平均情况:任意输入规模的期望运行次数 。
最好情况:任意输入规模的最小运行次数(下界)。

例如: 在一个长度为N数组中搜索一个数据x。
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)。

3、常见时间复杂度计算

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

函数执行了2N+10次,影响因素最大的是N,因此时间复杂度是:O(N)

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

函数执行了N+M次,N和M的影响程度相同,因此时间复杂度是:O(N+M)

3)实例3

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

函数执行的次数与N无关,因此时间复杂度为常数级别:O(1)

4)实例4

//strchr:在字符串中查找指定字符的首次出现位置
const char* strchr(const char* str, int character)
{
    while (*str)
    {
        if (*str == character)
            return str;
        else
            ++str;
    }
}

最坏情况下函数需要遍历整个字符串,循环次数为N,因此时间复杂度为:O(N)

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;                        //提前终止循环
    }
}

最坏的情况下,函数会执行N-1 + N-2 + ... + 2 + 1次,求和后就是N*(N-1)/2,影响因素最大的是N2,因此时间复杂度是:O(N2)

6)实例6

//二分查找
int BinarySearch(int* a, int n, int x)
{
    assert(a);
    
    int begin = 0;
    int end = n-1;  //注意:这里是闭区间 [begin, end]
    
    while (begin <= end)  //闭区间条件:begin <= end
    {
        int mid = begin + ((end-begin)>>1);  //安全的中间值计算,避免溢出
        
        if (a[mid] < x)
            begin = mid+1;  //右半区间:[mid+1, end]
        else if (a[mid] > x)
            end = mid-1;    //左半区间:[begin, mid-1]
        else
            return mid;     //找到目标值
    }
    
    return -1;  //未找到
}

二分查找的最坏情况是查找区间只剩下最后一个数或者找不到,因此执行次数应该为N/2/2.../2=1,因此 N= log ⁡ 2 N \log_2 N log2N,时间复杂度为:O( log ⁡ N \log N logN)

7)实例7

//阶乘
long long Fac(size_t N)
{
	if (0 == N)
		return 1;

	return Fac(N - 1) * N;
}

递归执行了N+1次,因此时间复杂度为:O(N)

8)实例8

//斐波那契数
long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

画图演示:

递归总共执行了2N-1-1 次,因此时间复杂度为:O(2N)

由于时间复杂度太高,我们一般不使用递归的方式计算斐波拉契数列,而是使用迭代,如下:

//斐波拉契数
long long Fib(int N)
{
	long f1 = 1;
	long f2 = 1;
	long f3 = 0;
	for (int i = 3; i < N; i++)
	{
		f3 = f1 + f2;
		f1 = f2;
		f2 = f3;
	}

	return f3;
}

现在的时间复杂度就变为了O(N)

三、空间复杂度

1、空间复杂度的概念

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

空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,空间复杂度其实算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

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

常见的空间复杂度有:O(1)、O(N)、O(N^2)。

2、常见空间复杂度计算

实例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(1)

实例2:

//递归求阶乘
long long Fac(size_t N)
{
	if (N == 0)
		return 1;

	return Fac(N - 1) * N;
}

每次递归的调用就会创建一个函数栈帧(空间),总共调用N+1次递归,就会开辟N+1个函数栈帧,因此空间复杂度为:O(N)

实例3:

//斐波拉契数
long long Fib(size_t N)
{
	if (N < 3)
		return 1;

	return Fib(N - 1) + Fib(N - 2);
}

同样每次递归调用都会开辟一块函数栈帧,但是在这里这个空间可以重复使用,总共开辟N-1个函数栈帧,因此空间复杂度为:O(N)

四、常见复杂度对比

一般算法常见的复杂度如下:
在这里插入图片描述

五、时间复杂度的oj练习

两道时间复杂度的oj练习题,如下:

1、消失的数字

点击链接:https://leetcode-cn.com/problems/missing-number-lcci/

在这里插入图片描述
这道题我们有三种思路,但是题目要求时间复杂度在O(N)内,因此我们需要分析一下。
思路一: 先将数组排序(qsort快速排序),再依次查找,如果下一个值不等于前一个+1,那么下一个值就是消失的数字。

时间复杂度分析: 快速排序的平均时间复杂度是O(N* log ⁡ N \log N logN),查找的最坏情况下时间复杂度是O(N),发现快速排序的时间复杂度影响程度更大,因此整体的时间复杂度为:O(N log ⁡ N \log N logN)。不满足题意舍去。

思路二: 求和0到N(未消失),再依次减去数组所有的元素值,最后剩下的就是消失的元素。这种情况下时间复杂度为O(N),满足题意。

int missingNumber(int* nums,int numsSize)
{
    int N=numsSize;
    int ret=(0+N)*(N+1)/2;
    for(int i=0;i<N;i++)
    {
        ret-=nums[i];
    }
    return ret;
}

经测试可以成功运行。但是有个问题就是N太大的时候存在溢出风险。

思路三: 采用异或(异或特性:任何数和0异或都等于本身,相同的数异或等于0,异或满足交换结合律),先用0与数组中的元素一一异或,再将这个整体与0到N的整数一一异或,最后剩下的就是消失的数。

int missingNumber(int* nums, int numsSize) 
{  
    int N=numsSize;
    int x=0;
    for(int i=0;i<N;i++)
    {
        x^=nums[i];
    }

    for(int j=0;j<=N;j++)
    {
        x^=j;
    }  
    return x;
}

函数执行了2N+1次,因此时间复杂度为O(N),满足题意。

2、轮转数组

点击链接:https://leetcode-cn.com/problems/rotate-array/
在这里插入图片描述

思路一:每次实现一次旋转,总共旋转k%N次即可。

如图所示:

void rotate(int* nums, int numsSize, int k) 
{
    int N=numsSize;
    k%=N;
    while(k--)
    {
        //旋转一次
        int temp=nums[N-1];
       for(int i=N-2;i>=0;i--)
       {
        nums[i+1]=nums[i];
       } 
       nums[0]=temp;
    }
}

函数会执行K*N次,因此时间复杂度为:O(K * N),当输入的数据很大时,时间复杂度就很大,就会超出时间限制。

如图所示:

思路二:采取三步反转法。

void reverse(int* a,int left,int right)
{
    while(left<right)
    {
        int temp=a[left];
        a[left]=a[right];
        a[right]=temp;
        left++;
        right--;
    }
}

void rotate(int* nums, int numsSize, int k) 
{
    int N=numsSize;
    k%=N;
    
    reverse(nums,0,N-k-1);
    reverse(nums,N-k,N-1);
    reverse(nums,0,N-1);
}

函数执行了N-k + k + N=2N次,因此时间复杂度为O(N),相比思路一时间复杂度就大大的减少了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于springboot的藏文古籍系统

博主介绍&#xff1a;高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实实在…

重构城市应急指挥布控策略 ——无人机智能视频监控的破局之道

在突发事件、高空巡查、边远区域布控中&#xff0c;传统摄像头常常“看不到、跟不上、调不动”。无人机智能视频监控系统&#xff0c;打破地面视角局限&#xff0c;以“高空布控 AI分析 实时响应”赋能政企单位智能化管理。在城市应急指挥中心的大屏上&#xff0c;一场暴雨正…

声音信号的基频检测(python版本)

import math import wave import array import functools from abc import ABC, abstractmethod import matplotlib import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec import os import sys# 设计模式部分 class PreprocessStrategy(ABC):"&q…

STM32 控制12VRGB灯带颜色亮度调节,TFTLCD显示

接了一个同学的小项目&#xff0c;要实现控制一个实体&#xff0c;控制灯带的亮度为红/绿/蓝/白/黄以及亮度的叠加。 时间要的比较急&#xff0c;要两天实现&#xff0c;因此不能打板&#xff0c;只能采用现有模块拼接。 一. 实施方案 一开始觉得很简单&#xff0c;就是使用五…

【JJ斗地主-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

《绩效管理》要点总结与分享

目录 绩效管理与目标设定 绩效管理的循环&#xff1a;PDCA 绩效目标的设定要点 绩效设定的工具&#xff1a;SMART法则 绩效跟踪与评估 刻板印象&#xff1a;STAR法 晕轮效应&#xff1a;对比评价法 近因效应&#xff1a;关键事项评估表 绩效面谈 面谈前准备工作 汉堡…

Microsoft前后端不分离编程新风向:cshtml

文章目录 什么是CSHTML&#xff1f;基础语法内联表达式代码块控制结构 布局页面_ViewStart.cshtml_Layout.cshtml使用布局 模型绑定强类型视图模型集合 HTML辅助方法基本表单验证 局部视图创建局部视图使用局部视图 高级特性视图组件依赖注入Tag Helpers 性能优化缓存捆绑和压缩…

【评测】用Flux的图片文本修改的PS效果

【评测】Flux的图片文本修改的PS效果 1. 百度图库找一张有英文的图片 2. 打开https://playground.bfl.ai/image/edit上传图片 3. 输入提示词 “change brarfant to goodbeer” 图片的文字被修改了

数据库管理-第334期 Oracle Database 23ai测试版RAC部署文档(20250607)

数据库管理334期 2024-06-07 数据库管理-第334期 Oracle Database 23ai测试版RAC部署文档&#xff08;20240607&#xff09;1 环境与安装介质2 操作标准系统配置2.1 关闭防火墙2.2 关闭SELinux2.3 关闭avahi-daemon2.4 时间同步配置 3 存储服务器配置3.1 配置本地yum源3.2 安装…

AI生成的基于html+marked.js实现的Markdown转html工具,离线使用,可实时预览 [

有一个markdown格式的文档&#xff0c;手头只有notepad的MarkdownPanel插件可以预览&#xff0c;但是只能预览&#xff0c;不能直接转换为html文件下载&#xff0c;直接复制预览的内效果又不太好&#xff0c;度娘也能找到很多工具&#xff0c;但是都需要在线使用。所以考虑用AI…

机器学习:load_predict_project

本文目录&#xff1a; 一、project目录二、utils里的两个工具包&#xff08;一&#xff09;common.py&#xff08;二&#xff09;log.py 三、src文件夹代码&#xff08;一&#xff09;模型训练&#xff08;train.py&#xff09;&#xff08;二&#xff09;模型预测&#xff08;…

【storage】

文章目录 1、RAM and ROM2、DRAM and SRAM2、Flash Memory&#xff08;闪存&#xff09;4、DDR and SPI NOR Flash5、eMMC6、SPI NOR vs SPI NAND vs eMMC vs SD附录——prototype and demo board附录——U盘、SD卡、TF卡、SSD参考 1、RAM and ROM RAM&#xff08;Random Acce…

JVM 垃圾回收器 详解

垃圾收集器 SerialSerial Old&#xff1a;单线程回收&#xff0c;适用于单核CPU场景ParNewCMS&#xff1a;暂停时间较短&#xff0c;适用于大型互联网应用中与用户交互的部分Paraller ScavengeParallel Old&#xff1a;吞吐量高&#xff0c;适用于后台进行大量数据操作G1&#…

FreeRTOS任务之深入篇

目录 1.Tick1.1 Tick的概念1.2 Tick与任务调度1.3 Tick与延时函数 2.任务状态2.1 运行状态 (Running)2.2 就绪状态 (Ready)2.3 阻塞状态 (Blocked)5.4 暂停状态 (Suspended)2.5 特殊状态&#xff1a;删除状态 (Deleted)5.6 任务状态转换2.7 实验 3.Delay函数3.1 两个函数3.2 实…

Linux 系统、代码与服务器进阶知识深度解析

在数字化时代&#xff0c;Linux 系统凭借其开源、稳定、安全的特性&#xff0c;成为服务器领域和软件开发的核心支柱。除了算法优化技巧&#xff0c;Linux 系统在网络服务、容器化技术、服务器安全等方面也蕴含着丰富的知识和实用技术。接下来&#xff0c;我们将深入探讨这些领…

人工智能--AI换脸

本文实现了一个简易的人脸交换程序&#xff0c;主要功能包括&#xff1a;1)检查所需的模型文件是否存在&#xff1b;2)使用预训练的Caffe模型检测图像中的人脸&#xff1b;3)将源图像的人脸区域通过泊松融合无缝地替换到目标图像上。程序通过OpenCV的DNN模块加载人脸检测模型&a…

NLP学习路线图(二十七):Transformer编码器/解码器

一、Transformer概览&#xff1a;抛弃循环&#xff0c;拥抱注意力 传统RNN及其变体&#xff08;如LSTM、GRU&#xff09;处理序列数据时存在顺序依赖的瓶颈&#xff1a;必须逐个处理序列元素&#xff0c;难以并行计算&#xff0c;且对长程依赖建模能力较弱。Transformer的革命…

【机器学习】支持向量机实验报告——基于SVM进行分类预测

目录 一、实验题目描述 二、实验步骤 三、Python代码实现基于SVM进行分类预测 四、我的收获 五、我的感受 一、实验题目描述 实验题目&#xff1a;基于SVM进行分类预测 实验要求&#xff1a;通过给定数据&#xff0c;使用支持向量机算法&#xff08;SVM&#xff09;实现分…

HA: Wordy靶场

HA: Wordy 来自 <HA: Wordy ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.128&#xff0c;靶场IP192.168.23.130 3&#xff0c;对靶机进行端口服务探…

中国移动6周年!

基站超过250万个 网络规模全球最大、质量最优 覆盖全国96%人口 在全国率先实现乡乡双千兆 服务用户超5.7亿 网络上下行均值接入速率均居行业首位 行业应用快速推广&#xff0c;数量超5万个 3CC、RedCap、通感一体、 无线AI改造等技术成熟商用 客户品牌持续升级&#x…