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

数据结构入门之复杂度

前言:终于来到了数据结构。要想学好数据结构,首先就要了解数据结构的复杂度。那么,什么是复杂度呢?

1 数据结构

所谓数据结构(Data Structure)就是计算机存储,组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合

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

那么如何来衡量一个算法的好坏呢?

2 复杂度

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

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

2.1 时间复杂度

定义:在计算机科学中,算法的时间复杂度是一个函数式T(N),它定量的描述了算法的运行时间。时间复杂度是衡量程序的时间效率,那为什么不去计算程序的运行时间呢?

原因有以下几点:

1.程序运行时间和编译环境,运行机器的配置都有关系。在同样的机器不同的编译器上运行时间也可能有所不同。

2.同一个算法程序在不同的机器上运行时间也会有所不同。

3.并且时间只能在程序写好后测试,不能写程序前通过理论思想计算评估。

那么函数式T(N)到底是什么呢?这个T(N)函数式计算的是程序的执行次数。假设计算机每条指令执行的时间基本一样(实际中有差别,但是差别不大),那么执行次数和运行时间成正相关。执行次数就可以代表程序时间效率的优劣。

#include<stdio.h>
#include<time.h>
int main()
{
	time_t ret1 = time(NULL);
	int arr[100000] = { 6,4,8,2,9,1,7,3,10,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
	time_t ret2 = time(NULL);
	printf("%lld\n", ret2 - ret1);
	return 0;
}

这段代码就很好的证明了上述观点。程序每次运行起来获取的时间不一定是一样的。接下来就是习题大集合。来看看下面代码的复杂度吧。

// 请计算⼀下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 (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
}

在这里插入图片描述

实际在计算时间复杂度时,计算的并不是程序执行的准确次数(这样做的意义也不大)。所以我们只需要计算程序执行的大概次数就可以了。复杂度的表示通常使用大O的渐进表示法

2.2 大O的渐进表示法

大O渐进表示法的规则

1.时间复杂度函数式T(N)中,只保留最高阶项,去掉低阶项。
因为N不断变大时,低阶项对结果影响越来越小,当N无穷大时,
就可以忽略不计了。
2.如果最高阶项存在且不是1,则去除这个项的系数。因为当N
不断变大时,这个系数对结果的影响越来越小,当N无穷大时,
就可以忽略不计了。
3.T(N)中如果没有N相关的项,只有常数项。则用常数1代替。

所以,上述Func1代码的时间复杂度是O(N^2)。

接下来的代码时间复杂度又是多少呢?

// 计算Func2的时间复杂度?
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);
}

在这里插入图片描述

根据大O的渐进表示法,Func2的时间复杂度是O(N)。

// 计算Func3的时间复杂度?
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);
}

在这里插入图片描述

Func3的时间复杂度分为三种情况:

1.M>>N,Func3的时间复杂度是O(M)

2.M<<N,Func3的时间复杂度是O(N)

3.M与N近似相等,Func3的时间复杂度是O(N)或O(M)

// 计算Func4的时间复杂度?
void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}

在这里插入图片描述

// 计算strchr的时间复杂度?
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;
}

在这里插入图片描述

总结:

最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
大O的渐进表示法在实际中一般情况关注的是算法的上界,也就是最坏运行情况
// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
	assert(a);
	for (size_t end = 0; end < n - 1; ++end)
	{
		int exchange = 0;
		for (size_t i = 0; i < end - i - 1; ++i)
		{
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}

在这里插入图片描述

因此,BubbleSort的时间复杂度是O(N^2)。

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

在这里插入图片描述

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
	if (0 == N)
	{
		return 1;
	}
	return Fac(N - 1) * N;
}

在这里插入图片描述

2.3 空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行期间因为算法的需要额外临时开辟的空间。空间复杂度并不是程序占用多少个Bytes的空间,因为常规情况下每个对象的大小差异不会很大,所以空间复杂度计算的是变量的个数

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

注意函数运行时所需要的栈空间在编译期间就已经确定好了,因此空间复杂度主要通过函数在运行时显示申请的额外空间来确定

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
	assert(a);
	for (size_t end = 0; end < n - 1; ++end)
	{
		int exchange = 0;
		for (size_t i = 0; i < end - i - 1; ++i)
		{
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}

BubbleSort函数所需要的栈空间在编译期间就已经确定好了,只需要关注函数在运行时所需要申请的额外空间。BubbleSort额外申请的空间有end,exchange等有限个局部变量,使用了常数个空间,因此BubbleSort空间复杂度是O(1)

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
	if(N == 0)
	return 1;
	return Fac(N-1)*N;
}

Fac递归调用了N次,开辟了N个栈帧空间,每个栈帧使用了常数个空间,因此空间复杂度为O(N)。

3 常见复杂度对比

在这里插入图片描述


http://www.kler.cn/news/365942.html

相关文章:

  • 论文速读:YOLO-G,用于跨域目标检测的改进YOLO(Plos One 2023)
  • 软考中级嵌入式系统设计师笔记分享(二)
  • 微信小程序性能优化 ==== 合理使用 setData 纯数据字段
  • 当有违法数据时,浏览器不解析,返回了undefined,导致数据不解析
  • 深入了解 kotlinx-datetime:配置与使用指南
  • ctfshow(41)--RCE/命令执行漏洞--或绕过
  • 数据结构与算法:贪心与相关力扣题455.分发饼干、376.摆动序列、53.最大子数组和(贪心+动态规划dp)、122.买卖股票的最佳时机Ⅱ
  • 25届电信保研经验贴(自动化所)
  • 基于 STM32 单片机的智能门禁系统创新设计
  • Java从List中删除元素的几种方式
  • 【C语言刷力扣】441.排列硬币
  • 基于行业分类的目标检测与跟踪系统
  • .NET 8 Web API从基础到提高全面示例
  • 电脑技巧:Rufus——最佳USB启动盘制作工具指南
  • 【代码随想录Day51】图论Part03
  • 第五十五章 安全元素的详细信息 - ReferenceList 详情
  • 可能是NextJs(使用ssr、api route)打包成桌面端(nextron、electron、tauri)的最佳解决方式
  • 浅谈人工智能之基于LLaMA-Factory进行Llama3微调
  • 2024.7最新子比主题zibll7.9.2开心版源码+授权教程
  • 08 设计模式-结构型模式-过滤器模式
  • Qt之hello world
  • SpringBoot面试热题
  • 麒麟v10 arm64 部署 kubesphere 3.4 修改记录
  • C#与Sqlite数据库
  • 01. 初识C++
  • 钉钉录播抓取视频