【C语言入门数据结构】时间复杂度和空间复杂度

简介: 时间复杂度和空间复杂度秒懂

🎈今日心语:你所看到的惊艳,都曾被平庸所历练。


数据结构指的是“一组数据的存储结构”,算法指的是“操作数据的一组方法”。

数据结构是为算法服务的,算法是要作用在特定的数据结构上的。


目录:



一、算法的复杂度

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

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

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

1. 时间复杂度

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

// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
    for (int j = 0; j < N ; ++ j)
    {
        ++count;
    }
}//这里嵌套了两个for循环,
for (int k = 0; k < 2 * N ; ++ k)
{
    ++count;
}
int M = 10;
while (M--)
{
    ++count;
}
    printf("%d\n", count);
}

可以看到上面代码中共出现了4个循环:

第一个for循环与第二个循环嵌套,所以执行次数为N2;

第三个循环执行次数为2*N;

第四个循环执行次数为10;

Func1 执行的基本操作次数

f(N) = N2 + 2*N + 10

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

我们发现N越大,后面系数和常量对整体f(n)的影响越小。

实际上现在,10和100000次,相差的时间都很小,这主要依赖于cpu的强大:

#include<stdio.h>
#include<time.h>
int main()
{
  size_t begin = clock();//开始计时
  size_t n = 0;
  for (size_t i = 0; i < 10; ++i)
  {
    ++n;
  }
  size_t  end = clock();//结束计时
  printf("%d毫秒\n", end - begin);//差为时间,单位是毫秒
  return 0;
}

执行0次结果:

执行100000次结果:

上面我们是在Debug环境下的测试,但是如果在Release环境下,即使是执行一亿次时间都是0毫秒!

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


1.1 大O的复杂度表示法

大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度。

当n无限大时,公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略,所以只需要记录一个最大量级就可以了。

即推导大O阶方法:

1、用常数1取代运行时间中的所有加法常数。

2、在修改后的运行次数函数中,只保留最高阶项。

3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

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

O(N^2)

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

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,只保留了最大量级,简洁明了的表示出了执行次数,而且对精确计算影响不大。

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

一、复杂度分析的4个概念

1.最坏情况时间复杂度:代码在最坏情况下执行的时间复杂度,即任意输入规模的最大运行次数(上界)。

2.最好情况时间复杂度:代码在最理想情况下执行的时间复杂度,即任意输入规模的最小运行次数(下界)。

3.平均时间复杂度:代码在所有情况下执行的次数的加权平均值,即任意输入规模的期望运行次数。

4.均摊时间复杂度:在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。

例如:在一个长度为N数组中搜索一个数据x

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到

代码复杂度在不同情况下出现量级差别时才需要区别这四种复杂度。大多数情况下,是不需要区别分析它们的。

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

1.2常见时间复杂度实例分析

多项式阶:随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括,

O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n2) (平方阶)(n3)(立方阶)

非多项式阶:随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差。包括,

O(2n)(指数阶)、O(n!)(阶乘阶)

2.空间复杂度

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

空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。

空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

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


如果还是不理解,请看下一篇内容 时间复杂度与空间复杂度练习题,通过习题,相信你会有进一步的理解!


这里我们关于【数据结构复杂度】的内容就介绍完了,文章中某些内容我们之前有介绍,所以只是一笔带过,还请谅解。下一篇文章,我们来通过例题来分析时间复杂度和空间复杂度
希望以上内容对大家有所帮助👀,如有不足望指出🙏

前路漫漫!努力变强💪💪 吧!!



相关文章
|
19天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
97 9
|
18天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
60 16
|
18天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
66 8
|
20天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
48 4
|
22天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
56 3
|
22天前
|
存储 算法 C语言
C语言数据结构(2)
【10月更文挑战第21天】
|
20天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
39 0
|
10天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
19 1
|
12天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
15天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。