初识《时间复杂度和空间复杂度》

news2024/11/14 23:11:00

目录

前言:

关于数据结构与算法

1.什么是数据结构?

2.什么是算法?

3.数据结构和算法的重要性

算法效率是什么?

1.怎样衡量一个算法的好坏呢?

2.算法的复杂度是个什么?

时间复杂度

1.时间复杂度的概念

2.大O的渐进表示法

3.举例说明(动手实践)

例1:

 例2:

例3:

例4:

例5:

例6:

例7:

例8:

总结:

空间复杂度

举例说明

例1

例2

例3 

总结


前言:

从本文开始,我们已经结束了之前对于C语言的初步学习,接下来我们就要运用我们学习过的C语言知识来实现问题的解决,因此我们不得不引入一大类————《数据结构与算法》。

接下来的我们将会进行大量刷题与知识理解,在这里我们一定不能松懈,一定要去动手解决自己不熟悉的部分。

关于数据结构与算法

1.什么是数据结构?

数据结构是指在计算机中组织和存储数据的方式,它是计算机科学的一个基本概念。数据结构涉及到的是数据如何在内存中进行存储和访问,以及对数据进行操作和处理的方法。在计算机科学中,数据结构广泛应用于算法设计和实现,数据库管理系统,编译器和操作系统等领域。常见的数据结构有数组、链表、树、图等。掌握数据结构对于编写高效、优化的程序和解决复杂问题至关重要。

2.什么是算法?

简单来说,算法是一组解决问题的步骤和规则。在计算机科学中,算法是用来解决计算问题的有限指令集合,即一种可用来求解某一问题的明确定义的方法。算法包括准确的定义、输入、输出、有限性、确切性和有效性等特征。在计算机程序设计中,算法通常被用来解决各种问题,如搜索、排序、优化、数据压缩等。算法的设计和实现是计算机科学和编程中非常重要的组成部分。

3.数据结构和算法的重要性

数据结构和算法是计算机科学中非常重要的概念,它们可以帮助我们更高效地解决实际问题。具体来说,以下是数据结构和算法的重要性:

  1. 提高代码质量:使用正确的数据结构和算法可以提高代码的可读性、可维护性,减少代码的复杂度。

  2. 优化程序性能:通过选择合适的数据结构和算法,程序可以更快、更高效地执行。

  3. 解决实际问题:数据结构和算法是解决实际问题的基础,例如搜索、排序、最短路径等问题。

  4. 掌握基本编程技能:学习数据结构和算法是掌握基本编程技能的一部分,对于从事编程相关工作的人员而言是必须的。

  5. 促进思维能力:学习数据结构和算法可以提高我们的逻辑思维能力,培养我们的分析和解决问题的能力。

综上所述,学习数据结构和算法对于计算机科学的学习和实践都非常重要。

算法效率是什么?

1.怎样衡量一个算法的好坏呢?

衡量一个算法的好坏有多种方法,以下是一些常见的:

  1. 时间复杂度:算法执行所需的时间是衡量其好坏的重要因素之一。时间复杂度是指算法执行所需的时间随着输入数据规模增加而增加的速度。一般来说,时间复杂度越低,算法的执行效率就越高。

  2. 空间复杂度:同样是衡量算法好坏的重要因素之一,空间复杂度是指算法执行所需的内存空间随着输入数据规模增加而增加的速度。一般来说,空间复杂度越低,算法的内存利用率越高。

  3. 精度和准确性:算法的输出结果应该与期望的结果一致,并且应该尽可能精确。这是针对特定问题的衡量标准,不同问题需要不同的衡量方式。

  4. 可读性和可维护性:算法代码应该易于理解和修改,并且应该易于维护。代码的可读性和可维护性有助于提高软件的可靠性和可用性。

  5. 可扩展性:算法应该易于扩展,以便在需要时可以轻松地进行修改和更新。可扩展性是在需求变化时重要的因素。

综上所述,衡量算法的好坏需要综合考虑多个因素,根据实际情况选择相应的衡量标准。

2.算法的复杂度是个什么?

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

时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算 机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。

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

时间复杂度

1.时间复杂度的概念

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

即:找到某条基本语句与问题规模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;
	}
	printf("%d\n", count);
}

 现在先来尝试计算机算以上代码的时间复杂度吧。

 

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

2.大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

推导大O阶方法: 1、用常数1取代运行时间中的所有加法常数。

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

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

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

O(N^2)

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

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

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)

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

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到

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

3.举例说明(动手实践)

例1:

// 计算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(2*N + M)

但是由大O的渐进表示方法,我们直接取

O(N)即可。

我们在后续做题时,应当先想出算法思路,再进行时间复杂度的计算,通过判断最坏情况,并选出时间复杂度最小的算法进行编写。

这里更要注意的是,我们在计算时间复杂度的时候,切忌不要先看代码,而是应当先去思考和想象,因为时间复杂度是一个抽象的概念,我们应当想编写代码的思路,而非代码本身!!

 例2:

// 计算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);
}

通过我上面所讲的,这里不难看出时间复杂度应当为O(N+M),即

O(N)

 

例3:

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

基本操作执行了10次,通过推导大O阶方法,时间复杂度为

 O(1)

 

例4:

// 计算strchr的时间复杂度?
const char * strchr(const char * str, int character)
{
	while (*str)
	{
		if (*str == character)
		{
			return str;
		}
		++str;
	}
}

 实例4基本操作执行最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为

O(N)

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

此例为冒泡排序,通过之前对数组的学习我们可以知道冒泡排序是效率最低,时间复杂度最高的算法,因为它要走n趟,并且在每一趟又要完遍历完n - i - 1个元素。

如果忘记了冒泡排序的实现,可以参考我之前写的blog

关于数组名-CSDN博客

如果你很熟练的话,通过我上述的描述你就知道该时间复杂度为O(N^2)了。

但是对于刚开始来学习的呢,有以下解释:

实例5基本操作执行最好N次,最坏执行了(N*(N+1))/2次,通过推导大O阶方法+时间复杂度一般看最坏,时间复杂度为 O(N^2) 

 最坏的情况是怎么算出来的?

这就要回顾我们在高中学习到对等差数列的求和公式。

即:

(首项 + 尾项)*(项数)/ 2

什么意思呢?

最坏的情况莫过于冒牌排序一个逆序数组,因为他对每个数字都进行了排序。

如此我们可以简化为:

 

将各个长度相加,不就是等差求和了吗,如此一来我们就理解了为什么是要等差求和。

所以最后的答案就是

O(N^2) 

例6:

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
	assert(a);
	int begin = 0;
	int end = n - 1;
	// [begin, end]:begin和end是左闭右闭区间,因此有=号
	while (begin <= end)
	{
		int mid = begin + ((end - begin) >> 1);
		if (a[mid] < x)
			begin = mid + 1;
		else if (a[mid] > x)
			end = mid - 1;
		else
			return mid;
	}
	return -1;
}

此例为二分查找的代码,答案为O(logN)

对这道题我们可以用折纸再展开的方式来理解。

在这里我们先从代码的角度先讲解一下为什么是logN。

首先,我们要分析最坏的情况。

最坏的情况莫过于在数组经历二分查找后的最后一个元素为我们要查找的数。

所以我们就有以下的图:

 所以最终的答案是

O(logN)

我们在这里对2会进行省略。

虽然二分查找的时间复杂度很低,但是我们使用该算法之前要对其进行排序。

例7:

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

	return Fac(N - 1)*N;
}

例7通过计算分析发现基本操作递归了N次,时间复杂度为

O(N)

这个不难理解,我在这里不过多赘述。

例8:

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

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

 对于斐波那契数列的递归实现,其实和细胞分裂很像,一生二,二生四,四生八等等等,这种以指数形式增加的。

所以通过思想来看,不难看出时间复杂度为

O(2^N)

总结:

以上就是关于时间复杂度的一些例题,仅通过这些无法较好的彻底学会使用时间复杂度,后续我们在刷题的同时,也会适当的对时间复杂度进行讲解。 

要记住,首先看思路再看代码!

空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。 空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。 空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。 注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因 此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

由于空间复杂度的需求不是特别的重视,我们在以后的代码编写中,经常会实现牺牲空间换取时间的操作,这些都已是家常便饭的操作。

接下来我们就来分析一下三种代码的空间复杂度。

举例说明

例1

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

实例1使用了常数个额外空间,所以空间复杂度为

O(1)

例2

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

实例2动态开辟了N个空间,空间复杂度为

O(N)
 

例3 

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

实例3递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为

O(N)

总结

时间复杂度是算法执行所需时间的度量,通常使用大 O 符号表示。它表示当输入规模变大时,算法执行时间增长的快慢程度。时间复杂度越小,算法执行速度越快。

空间复杂度是算法执行所需内存的度量,也通常使用大 O 符号表示。它表示当输入规模变大时,算法所需内存增长的快慢程度。空间复杂度越小,算法所需内存越少。

在设计算法时,需要充分考虑时间和空间复杂度。如果一个算法时间复杂度较小,但空间复杂度过高,可能会导致程序在执行时占用过多的内存而造成程序崩溃。因此,需要在时间复杂度和空间复杂度之间取得一个平衡,使得算法在执行时既能保持较快的速度,又能占用尽可能少的内存。

 这部分内容会比较抽象,但是我们需要多多练习,数据结构和算法的内容是这样的抽象的,所以我们更不应该放弃。

这些部分是人发现的算法,既然是人那么我也可以,现在想不明白不代表明天不明白,只要肯坚持就不会有想不出来的。

记住,

坐而言不如起而行!

Action speak louder than words!

以下是这篇文字所需要的代码:

Time_complexity_and_Space_complexity_CSDN/Time_complexity_and_Space_complexity_CSDN/test_10_27.c · 无双/Data structures amd algorithms - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1141479.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Glide限定onlyRetrieveFromCache取内存缓存submit超时阻塞方式,Kotlin

Android Glide限定onlyRetrieveFromCache取内存缓存submit超时阻塞方式,Kotlin import android.os.Bundle import android.util.Log import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.b…

RK3568-pcie接口

pcie接口与sata接口 pcie总线pcie总线pcie控制器sata控制器nvme设备sata设备nvme协议ahci协议m-key接口b-key接口RC模式和EP模式 RC和EP分别对应主模式和从模式,普通的PCI RC主模式可以用于连接PCI-E以太网芯片或PCI-E的硬盘等外设。 RC模式使用外设一般都有LINUX驱动程序,安…

LLM系列 | 22 : Code Llama实战(下篇):本地部署、量化及GPT-4对比

引言 模型简介 依赖安装 模型inference 代码补全 4-bit版模型 代码填充 指令编码 Code Llama vs ChatGPT vs GPT4 小结 引言 青山隐隐水迢迢&#xff0c;秋尽江南草未凋。 小伙伴们好&#xff0c;我是《小窗幽记机器学习》的小编&#xff1a;卖热干面的小女孩。紧接…

36基于matlab的对分解层数和惩罚因子进行优化

基于matlab的对分解层数和惩罚因子进行优化。蚁狮优化算法优化VMD,算术优化算法优化VMD&#xff0c;遗传优化算法优化VMD&#xff0c;灰狼优化算法优化VMD&#xff0c;海洋捕食者优化算法优化VMD&#xff0c;粒子群优化VMD&#xff0c;麻雀优化算法优化VMD&#xff0c;鲸鱼优化…

如何使用 nvm-windows 这个工具来管理你电脑上的Node.js版本

nvm-windows 是一个用于管理在 Windows 上安装的多个 Node.js 版本的工具。以下是安装和使用 nvm-windows 的步骤&#xff1a; 第1步&#xff1a;下载 nvm-windows 访问 nvm-windows 的 GitHub发布页面.下载最新版本的 nvm-setup.zip 文件。 第2步&#xff1a;安装 nvm-wind…

KV STUDIO的安装与实践(一)

目录 什么是KV STUDIO&#xff1f; 如何安装KV STUDIO&#xff1f; 如何学习与使用KV STUDIO&#xff08;在现实中的应用&#xff09;&#xff1f; 应用一&#xff08;在现实生活中机器内部plc的读取与替换&#xff09; 读取 KV STUDIO实现显示器的检测&#xff01;&#…

微信小程序发货信息录入

微信小程序发货信息录入 一、发货信息录入接口 1、 用户交易后&#xff0c;默认资金将会进入冻结状态&#xff0c;开发者在发货后&#xff0c;需要在小程序平台录入相关发货信息&#xff0c;平台会将发货信息以消息的形式推送给购买的微信用户。 2、 如果你已经录入发货信息…

lossBN

still tips for learning classification and regression关于softmax的引入和作用分类问题损失函数 - MSE & Cross-entropy⭐Batch Normalization&#xff08;BN&#xff09;⭐想法&#xff1a;直接改error surface的landscape&#xff0c;把山铲平feature normalization那…

夯实c语言基础

夯实c语言基础 转义字符 • \? &#xff1a;在书写连续多个问号时使⽤&#xff0c;防⽌他们被解析成三字⺟词&#xff0c;在新的编译器上没法验证了。 • \ &#xff1a;⽤于表⽰字符常量 • \" &#xff1a;⽤于表⽰⼀个字符串内部的双引号 • \\ &#xff1a;⽤于表…

分享微信卸载重装恢复记录的3个方法!

微信是一款功能丰富、便捷实用的通信应用程序&#xff0c;为用户提供了方便、快速的即时通讯功能。无论您身在何处&#xff0c;通过微信&#xff0c;您可以轻松与家人、朋友保持紧密联系。有时候&#xff0c;为了能扩大手机内存空间&#xff0c;一些小伙伴可能会选择把微信卸载…

Sql Server中的表组织和索引组织(聚集索引结构,非聚集索引结构,堆结构)

正文 SqlServer用三种方法来组织其分区中的数据或索引页&#xff1a; 1、聚集索引结构 聚集索引是按B树结构进行组织的&#xff0c;B树中的每一页称为一个索引节点。每个索引行包含一个键值和一个指针。指针指向B树上的某一中间级页&#xff08;比如根节点指向中间级节点中的…

【软考系统架构设计师】2023年系统架构师冲刺模拟习题之《软件工程》

在软考中软件工程模块主要包含以下考点&#xff1a; 文章目录 软件过程模型&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;逆向工程&#x1f31f;基于构件的软件工程&#x1f31f;&#x1f31f;软件开发与软件设计与维护净室软件工程软件模型软件需求 软件过程模型&am…

Makefile 基础教程:从零开始学习

在软件开发过程中&#xff0c;Makefile是一个非常重要的工具&#xff0c;它可以帮助我们自动构建程序&#xff0c;管理程序依赖关系&#xff0c;提高开发效率。本篇博客将从基础开始&#xff0c;介绍Makefile的相关知识&#xff0c;帮助大家快速掌握Makefile的使用方法 Makefil…

专转本VS工作,两年后有什么区别?

很多同学在面对“是否要专转本”这件事上&#xff0c;还在摇摆不定&#xff0c;抱着不转本就工作的想法&#xff0c;觉得“工作赚钱”会更好。选择转本还是进入社会工作&#xff0c;这可能是每个大三生都有过思索的。之所以纠结&#xff0c;无非就是想要提升学历但害怕考不上&a…

【Javascript】ajax(阿甲克斯)

目录 什么是ajax? 同步与异步 原理 注意 写一个ajax请求 创建ajax对象 设置请求方式和地址 发送请求 设置响应HTTP请求状态变化的函数 什么是ajax? 是基于javascript的一种用于创建快速动态网页的技术&#xff0c;是一种在无需重新加载整个网页的情况下&#xff0c…

『力扣刷题本』:合并两个有序链表(递归解法)

一、题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#x…

K8s概念汇总-笔记

目录 1.Master 1.1在Master上运⾏着以下关键进程 2.什么是Node? 1.2在每个Node上都运⾏着以下关键进程 3.什么是 Pod ? 4. 什么是Label &#xff1f; 5.Replication Controller 6.Deployment 6.1Deployment的典型场景&#xff1a; 7.Horizontal Pod Autoscaler TODO…

Node.js的基本概念node -v 和npm -v 这两个命令的作用

Node.js 是一个开源且跨平台的 JavaScript 运行时环境&#xff0c;它可以让你在服务器端运行 JavaScript 代码。Node.js 使用了 Chrome 的 V8 JavaScript 引擎来执行代码&#xff0c;非常高效。 在 Node.js 出现之前&#xff0c;JavaScript 通常只在浏览器中运行&#xff0c;用…

物联网和互联网医院小程序:如何实现医疗设备的远程监测和管理?

物联网&#xff08;IoT&#xff09;技术的发展为医疗设备的远程监测和管理提供了巨大的机会。结合互联网医院小程序&#xff0c;我们可以实现对医疗设备的远程访问、监控和管理&#xff0c;从而提高医疗服务的质量和效率。本文将介绍如何实现医疗设备的远程监测和管理&#xff…

漏洞复现-phpmyadmin_SQL注入 (CVE-2020-5504)

phpmyadmin SQL注入 _&#xff08;CVE-2020-5504&#xff09; 漏洞信息 CVE-2020-5504sql注入漏洞Phpmyadmin 5.00以下 描述 ​ phpMyAdmin是Phpmyadmin团队的一套免费的、基于Web的MySQL数据库管理工具。该工具能够创建和删除数据库&#xff0c;创建、删除、修改数据库表&…