【数据结构与算法基础】——算法复杂度

简介: 【数据结构与算法基础】——算法复杂度

前言

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

一、算法效率

       如何去衡量一个算法的好坏?

       算法在编写成可执行程序后,运行时消耗时间和空间资源。因此,一般从时间和空间两个维度来衡量,即时间复杂度和空间复杂度。

时间复杂度和空间复杂度

       时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行时所需要的额外空间。      

       在计算机发展的早期,计算机的存储容量很小。所以对复杂度很是在乎。但是计算机行业的迅速发展,计算机存储容量已经达到很高的程度,所以我们并不用特别关注算法的空间复杂度

二、时间复杂度

       时间复杂度是指算法在执行过程中,所需要的时间资源和问题规模之间的关系,主要衡量算法的运行效率,用来估算算法在不同规模下的运行时间

       时间复杂度用大O的渐进表示法来表示

2.1时间复杂度的计算

       算法的时间复杂度是一个函数式T(N),它定量描述了该算法的运行时间。

实际上,我们计算时间复杂度时,计算主要涉及到以下几个方面

基本操作次数: 时间复杂度的计算通常关注算法中执行的基本操作次数,例如赋值操作、比较操作、算术运算等。通常将这些操作的数量与输入规模相关联。

循环结构: 算法包含循环结构,需要考虑循环的迭代次数以及每次迭代中的基本操作数量。

递归调用: 对于递归算法,需要考虑递归的深度以及每次递归调用的时间复杂度。通常使用递归方程(递归关系式)来表示递归算法的时间复杂度。

分支结构: 如果算法包含分支结构,需要考虑每个分支的执行次数以及分支中的基本操作数量。

输入规模: 时间复杂度的计算通常与输入规模有关。输入规模表示算法操作的数据量或问题的大小,通常用符号n表示。

       说白了,算法复杂度其实就是计算基本操作的执行次数

看一个案例,来计算时间复杂度

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

这里Func函数基础语句执行次数 T(N)=N^2+2N+10

大O的渐进表示法表示就成了 O(N^2)

大O的渐进表示法

1 . 时间复杂度的函数式T(N)中,只保留最高阶项,去掉那些低阶项

2 . 如果最高项存在且不是1,则去掉这个项的常数系数

3 . T(N)中如果没有N的相关项,只要常数项,用常数1来取代所有加法常数

2.2、时间复杂度计算实例

2.2.1、示例一

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

Func1函数基本操作次数

        T(N)= 2N+10

       大O渐进表示法  Func1的时间复杂度为:O(N)

2.2.2、示例二

void Func2(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);
}

Func1函数基本操作次数

       T(N)= M+N

       因为这里无法确定M和N的大小,所以有大O渐进表示法 就为:O(M+N)

2.2.3、示例三

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

Func3函数的基本操作次数

       T(N)=100

       用大O渐进表示法 表示 O(1)

2.2.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;
}

这里计算strchr函数的基本操作次数      

       如果查找的字符在字符串的第一个位置(靠前的位置)则T(N)= 1

       如果查找的字符在字符串最后 则T(N)=  N

       如果查找的字符在字符串中间位置 则T(N)= N/2

这样用大O渐进表示法就要三种情况

情况一: O(1)        情况二: O(N)        情况三: O(N)

这里我们就会发现,有些算法的时间复杂度存在多种情况

       最好情况 : 任意出入规模的最小运行次数(下界)

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

       平均情况: 任意输入规模的期望运行次数

大O渐进表示法在实际情况中一般关注的是算法的上界,也就是最坏运行情况。

所以这里strchr函数的时间复杂度为:O(N)

2.2.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;
  }
}

冒泡排序的时间复杂度

       如果数组有序为升序,则T(N) = N (最好情况)

       如果数组有序为降序,则T(N) = (N*(N+1))/2 (最坏情况)

因此BubbleSort的时间复杂度为 O(N^2)

2.2.6、示例六

void func4(int n)
{
  int cnt = 1;
  while (cnt < n)
  {
    cnt *= 2;
  }
}

Func4函数

       n=2,执行次数为1

       n=4,执行次数为2

       n=16,执行次数为4

       当执行次数为x时,n=2^x

       所以执行次数 x=

所以时间复杂度为:

这里也可以写成log n

       当N接近无穷大时,底数的大小对结果影响不大。因此,一般情况下不管底数为多少都可以省略不写,写成log n

2.2.7、示例七

long long Fac(size_t N)
{
  if (0 == N)
    return 1;
  return Fac(N - 1) * N;
}

这里每一次调用Fac函数的时间复杂度为O(1)

       而一共有n次递归,所以阶乘递归的时间复杂度为O(N)

三、空间复杂度

       空间复杂度也是一个数学表达式,是对一个算法在运行过程中因为算法的需要额外临时开辟的空间

       空间复杂度表示程序占用了多少bytes的空间,因为常规情况下每个对象大小 差异不会很大,所以空间复杂度计算的是变量的个数

       空间复杂度也使用大O渐进表示法

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

空间复杂度计算

示例一

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)

示例二

long long Fac(size_t N)
{
  if (N == 0)
    return 1;
  return Fac(N - 1) * N;
}

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

       因此时间复杂度为:O(N)

感谢各位大佬支持并指出问题,


相关文章
|
24天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
17天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
4天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
1天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
241 12
|
19天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
21天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2579 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
3天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
169 2
|
1天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
101 65
|
21天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1578 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
4天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
256 2