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

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

前言

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

一、算法效率

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

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

时间复杂度和空间复杂度

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

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

二、时间复杂度

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

       时间复杂度用大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)

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


相关文章
|
2月前
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
66 1
|
2月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
77 0
|
10月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
405 1
|
11月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
600 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
7月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
193 30
|
7月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
297 25
|
7月前
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
278 23
|
10月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
193 33
|
8月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
259 3
|
10月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
220 19

热门文章

最新文章