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

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

前言

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

一、算法效率

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

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

时间复杂度和空间复杂度

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

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

二、时间复杂度

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

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

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


相关文章
|
20天前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
55 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
23天前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
19 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
16天前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
30 4
|
23天前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
17 0
数据结构与算法学习十四:常用排序算法总结和对比
|
23天前
|
存储 缓存 分布式计算
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
这篇文章是关于数据结构与算法的学习指南,涵盖了数据结构的分类、数据结构与算法的关系、实际编程中遇到的问题以及几个经典的算法面试题。
26 0
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
|
1月前
|
机器学习/深度学习 存储 算法
【初阶数据结构】算法效率大揭秘 | 时间与空间复杂度的深度剖析
【初阶数据结构】算法效率大揭秘 | 时间与空间复杂度的深度剖析
|
23天前
|
机器学习/深度学习 搜索推荐 算法
探索数据结构:初入算法之经典排序算法
探索数据结构:初入算法之经典排序算法
|
23天前
|
算法 Java 索引
数据结构与算法学习十五:常用查找算法介绍,线性排序、二分查找(折半查找)算法、差值查找算法、斐波那契(黄金分割法)查找算法
四种常用的查找算法:顺序查找、二分查找(折半查找)、插值查找和斐波那契查找,并提供了Java语言的实现代码和测试结果。
17 0
|
27天前
|
存储 算法 Java
数据结构和算法--分段树
数据结构和算法--分段树
12 0
|
30天前
|
算法 Java 测试技术
数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
17 0