当前位置: 首页 > news >正文

第一章 时间复杂度和空间复杂度

初阶数据结构

第一章 时间复杂度和空间复杂度


文章目录

  • 初阶数据结构
  • 前言
  • 一、时间复杂度
    • 1、什么是时间复杂度?
    • 2、大O的渐进表示法
    • 3、常见的时间复杂度例题
      • 例一
      • 例二
      • 例三
      • 例四
      • 例五
      • 例六
      • 例七
  • 二、空间复杂度
    • 1、什么是空间复杂度?
    • 2、例题:
      • (1)例一:
      • (2)例二:
      • (3)例三:
      • (4)例四:


前言

本系列文章,将基于C语言实现一些初阶的数据结构,目的是帮助大家熟悉C语言的同时能够入门数据结构。


一、时间复杂度

1、什么是时间复杂度?

时间复杂度,顾名思义,计算的是代码运行所花费的时间。但是我们知道,一个程序是在电脑上运行的,而电脑不同,同一个程序的运行时间也是不同的。同样的一套程序,我们用十年前的电脑和现在的电脑分别运行,最终的结果肯定是不同的。
所以,此处所说的时间复杂度并不是真正的代码运行所需的时间,而是一个程序中 代码所运行的次数 。然后将这个次数写成一个 数学函数表达式。此时,这个表达式就是这个程序的时间复杂度。

例如下面代码:

#include<stdio.h>
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		printf("%d ",i);
	}
	int m=10;
	while(m--)
	{
		printf("hehe\n");
	}
	return 0;
}

我们来算一算上面代码的运行次数,即时间复杂度。
在这里插入图片描述
在计算代码的运行次数的时候,我们一般忽略掉循环的头部和尾部。
所以,我们通过上面的图示我们发现,这个程序的时间复杂度为:F(n)= n+13

2、大O的渐进表示法

我们看下面的代码:

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++;
	}
	printf("%d\n",count);
}

我们计算一下上面代码的执行次数:
在这里插入图片描述
我们发现最终的运行次数为:F(n)=N*N+2N+13
我们不妨给这里的N进行赋值操作:

  • N=10 F(N)=133
  • N=100 F(N)=10213
  • N=1000 F(N)=1002013
    我们发现随着我们N数值的增长,N*N的占比越来越大,剩余项的大小完全可以忽略掉。
    所以我们此时只需要保留该函数表达式中的最高次项,其余完全可以忽略掉。基于这种想法,这里就出现了一种方法叫做大O渐进表示法,也可以成为大O记法

大O记法的方式:

- 仅仅保留最高次项。

- 最高次项的系数为1。

- 假设代码的运行次数为常数,那么统一记作O(1) 。


那么上述代码的运行次数的函数表达式可以简化为:O(N2)

另外有些时候,算法的运行次数是不确定的,例如我们利用遍历的方式在长度为N的数组中寻找一个数字,那么运气好的话,我们要找的数字就是第一个元素,此时算法的复杂度是O(1)。倘若我们运气不好的话,我们的要找的元素就是最后一个元素。此时我们算法的时间复杂度就是O(N)
那么我们在最坏和最好的情况之间取一个平均值,即N/2,但我们利用大O记法记录时,依旧是O(N)

从上面这个例子中,我们就能发现,我们算法的时间复杂度可以分为三种情况:

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

3、常见的时间复杂度例题

例一

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

在这里插入图片描述

例二

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

在这里插入图片描述

例三

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

在这里插入图片描述

例四

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

在这里插入图片描述

例五

int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
while (begin < end)
{
 int mid = begin + ((end-begin)>>1);
 if (a[mid] < x)
 begin = mid+1;
 else if (a[mid] > x)
 end = mid;
 else
 return mid;
}
return -1;
}

在这里插入图片描述

例六

long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N-1)*N;
}

在这里插入图片描述

例七

long long Fibonacci(size_t N)
{
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}

在这里插入图片描述
我们发现上面的题目中,很多题可以从表面上看出来其时间复杂度,但是有一些题目,例如:冒泡排序、斐波那契、二分查找等等算法,需要我们通过其背后的思想,通过画图等方式算出其时间复杂度,不能想当然。

二、空间复杂度

1、什么是空间复杂度?

空间复杂度是对一个算法在运行过程中临时占用的存储空间大小的量度,空间复杂度不是程序占用了多少字节,而是创建变量的个数。空间复杂度的表示方法依旧采用大O渐进表示法。

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

分析:
在这里插入图片描述
我们来分析一下上述代码:
这个算法是为了实现排序。无论采用什么排序算法,函数中的形参都是存在的。所以形参并不是我们为了实现这个算法而创建的。那么为了实现冒泡排序的算法,我们临时创建的变量有图中的三个。注意:for循环内的exchange不断地重复创建,只算一次。我们可以理解成这个变量在循环的过程中不断地创建销毁,使用的都是一块空间。综上,这个代码的是时间复杂度是O(1)

(2)例二:

// 计算阶乘递归Factorial的空间复杂度?
long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N-1)*N;
}

在这里插入图片描述

(3)例三:

long long* Fibonacci(size_t n)
{
	if (n == 0)
		return NULL;
	long long* fibArray =
		(long long*)malloc((n + 1) * sizeof(long long));
	fibArray[0] = 0;
	fibArray[1] = 1; for (int i = 2; i <= n; ++i)
	{
		fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
	}
	return fibArray;
}

在这里插入图片描述

(4)例四:

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
	if (N < 3)
		return 1;

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

在这里插入图片描述
我们发现斐波那契函数递归展开来以后是非常庞大的。呈现指数型增长!而每个函数的内部的空间复杂度都是O(1)。难道空间复杂度是
O(2n)吗?其实为了节约空间,系统不会直接展开所有,而是如图中的绿线所示,每次展开一条递归路线,当这个递归路线到底销毁后再开启另外一条。而每一条的空间复杂度是O(N)* O(1)=O(N)。而这些空间是重复利用的。所以递归最终的空间复杂度是 O(N)

相关文章:

  • 【论文阅读】SimGNN:A Neural Network Approach to Fast Graph Similarity Computation
  • Spring源码分析之AOP
  • 【0136】【libpq】startup packet应用机制及构建过程(6)
  • 如今Android 工作难找,面试也难~ 这是在劝退吗?
  • WebShell后门检测与WebShell箱子反杀
  • Java毕业设计选题推荐 SpringBoot毕设项目分享
  • 【Linux kernel/cpufreq】framework ----cpufreq core(1)
  • 一文2000字手把手教你自动化测试平台建设分享
  • 国务院:电子印章跨地区跨部门互信互认,契约锁助力企业办事提效
  • 同程内网流传的分布式凤凰缓存系统手册,竟遭GitHub强行开源下载
  • 【Hack The Box】windows练习-- devel
  • 山西大同大学技术会,大同大学的家!
  • verilog--用于电路设计--0
  • 完全二叉搜索树
  • 每天一个小细节:UDP协议特点与报文结构
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • gf框架之分页模块(五) - 自定义分页
  • JAVA之继承和多态
  • Java知识点总结(JavaIO-打印流)
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Map集合、散列表、红黑树介绍
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • python3 使用 asyncio 代替线程
  • windows下mongoDB的环境配置
  • 看域名解析域名安全对SEO的影响
  • 漂亮刷新控件-iOS
  • 使用 @font-face
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • ​queue --- 一个同步的队列类​
  • # .NET Framework中使用命名管道进行进程间通信
  • $GOPATH/go.mod exists but should not goland
  • (Qt) 默认QtWidget应用包含什么?
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (力扣)循环队列的实现与详解(C语言)
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转)EOS中账户、钱包和密钥的关系
  • (转)linux 命令大全
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • .bat批处理(六):替换字符串中匹配的子串
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .Net(C#)自定义WinForm控件之小结篇
  • .NET开发者必备的11款免费工具
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • ??在JSP中,java和JavaScript如何交互?
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)
  • [ NOI 2001 ] 食物链
  • [ 网络通信基础 ]——网络的传输介质(双绞线,光纤,标准,线序)
  • [BZOJ 3531][Sdoi2014]旅行(树链剖分+线段树)
  • [C][数据结构][树]详细讲解
  • [C++] 多线程编程-thread::yield()-sleep_for()
  • [Hive] 常见函数
  • [IE9] GPU硬件加速到底是实用创新还是噱头