草庐IT

数据结构--时间复杂度和空间复杂

includeevey 2024-07-06 原文

目录

前言

什么是数据结构

 数据结构与数据库

什么是算法

算法和数据结构的关系

时间复杂度

算法的复杂度

时间复杂度的概念        

大O的渐进表示法

常见时间复杂度计算举例

空间复杂度

空间复杂度的概念

常见空间复杂度计算举例

 常见复杂度对比

 复杂度的oj练习


前言

        随着近些年计算机快速的发展,对程序员来说入职要求也越来越高了,公司对我们的代码能力要求也越来越高了,大厂笔试中几乎全是算法题而且难度大,中小厂的笔试中才会有算法题,所以现在也越来越卷。为了能有一份好工作,我们必须手撕数据结构。难道就只是入职也一好处吗?显然不是的,学好算法不论对你思考问题的方式还是对你编程的思维都会有很大的好处。

什么是数据结构

        数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法索引技术有关。

                                                                                                                              ---- 来自百度

 数据结构与数据库

那么数据结构与数据库有什么关系呢?

数据结构:内存中管理数据--增删改查

数据库:    磁盘中管理数据--增删改查

什么是算法

        算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。列如在我学习c语言中:二分查找,冒泡等

算法和数据结构的关系

        数据结构和算法是相辅相成的。数据结构是为算法服务的,算法要作用在特定的数据结构之上。如果不在的基础上操作、构建算法,孤立存在的数据结构就是没用的。

时间复杂度

算法的复杂度

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

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

时间复杂度的概念        

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

大O的渐进表示法

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

推导大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的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

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

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

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

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

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

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到

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

常见时间复杂度计算举例

例题1

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

计算次数时间复杂度函数式:2*N+10

时间复杂度:O(N)

对于一个极大的数来说,乘一个常数与加一个常数都不能改变他的量级。比如一个亿万富翁,给他加几万或者在翻一倍,他变了好像又没有变,他还是亿万富翁。

例题2

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)

如果M远大于N ,时间复杂度:0(M);  N远大于M ,时间复杂度:O(N); M与N一样大,时间复杂度O(M)或者O(N)

例题3

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

时间复杂度:O(1)

这里的1并不是1次,代表的是常数次

例题4

const char * strchr(const char * str, int character);                        ----查找字符串的字符

时间复杂度:O(N)

最好次数1次找到,最坏次数N次找到,平局次数N/2,在实际情况下我们一般关注的是最坏运行情况,所以数组中搜索数据的时间复杂度是O(N)。有这样一句比较有哲理的话:用悲观的心态过积极的人生。在生活中做最坏的打算也不一定有最坏的结果。

例题5

#include  <stdio.h>

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

 

时间复杂度:O(N^2)

计算过程:N-1+N-2+......+2+1=N*(N-1)/2

例题6

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)

 计算过程:N/2/2..../2=1 ---> N=2^x --->x=log(2)N

例题7

long long Fac(size_t N)
{
	if (0 == N)
		return 1;
	return Fac(N - 1)*N; 
}

时间复杂度:O(N)

计算过程:Fac(N)->Fac(N-1)->Fac(N-2)->.......->Fac(2)->Fac(1)

例题8

long long Fib(size_t N)
{ 
	if (N < 3) 
	return 1;  

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

 时间复杂度:O(2^N)

我们发现递归有的时候并不是很好,时间复杂度比较大,如果这里传参比较大,跑的时间就会很久。所当我们不用递归实现如下:

long long Fib(size_t N)
{
	if (N < 3)
		return 1;
	long long f1 = 1, f2 = 1, f3 = 0;
	for (int i = 3; i <= N; i++)
	{
		f3 = f1 + f2;
		f1 = f2;
		f2 = f3;
	}
	return f3;
}

时间复杂度:O(N)

空间复杂度

空间复杂度的概念

        空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法

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

常见空间复杂度计算举例

例题1

#include <stdio.h>

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

空间复杂度:O(1)

这里需要理解:算法在运行过程中临时占用存储空间(额外)大小的量度,这里这开辟exchange,end,i 三个零时变量,因为大O渐进表示法,3为常数所以为1 。 

例题2

long long Fac(size_t N)//递归
{ 
    if(N == 0)

     return 1; 
     return Fac(N-1)*N;
}

空间复杂度:O(N) 

例题3

#include <stdio.h>

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

 空间复杂度:O(N)

这里就需要到了函数栈帧的知识了,当进行计算的时候会开辟空间,但是空间可以重复利用,我们知道在栈上开辟空间是有序的(进栈和出栈),比如:当需要开辟空间的时候系统会把开辟空间的权限给打卡,操作完成后进行退栈,有相当于系统将权限回收默认了那块空间报销,如果下次开辟空间的时候系统打开的空间还是原来的空间,并不是清理而是进行覆盖,直接在上面使用。

栈帧过程:

        这里我们我们先递归到最底层(一直开辟空间到N),然后再在N层上又进行进栈和退栈的操作,我们也可以想想如果一直开辟空间系统可能也会崩溃。

上述函数运行顺序:

 常见复杂度对比

 

 复杂度的oj练习

练习题:力扣

//方法1
int cmp(void* dest,void* sce)
{
    return *(int*)dest-*(int*)sce;
}

int missingNumber(int* nums, int numsSize){
    qsort(nums,numsSize,sizeof(int),cmp);

    int sum=0;
    for(int i=0;i<numsSize;i++)
    {
        if(nums[i]!=i+1)
        {
            sum= i+1;
        }
    }
    return sum;
}
//方法2
int missingNumber(int* nums, int numsSize)
{
	//前n项和 - 数组和 = 消失的数字
	int sum = 0;
	int miss = 0;
	sum = (1 + numsSize)*numsSize / 2;
	int i = 0;
	for (i = 0; i < numsSize; i++)
	{
		miss += nums[i];
	}
	miss = sum - miss;

	return miss;
}

有关数据结构--时间复杂度和空间复杂的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  6. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  8. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐