数据结构_时间复杂度/空间复杂度

news2024/9/22 20:17:22

目录

1. 数据结构在学什么

2. 数据结构的基本概念

3. 算法和算法评价

3.1 算法的基本概念

3.2 算法的特征

3.3 算法效率的度量

3.3.1 时间复杂度

3.3.2 空间复杂度


1. 数据结构在学什么

常言道:学以致用;学习完基本C语言程序,我们希望通过数据结构的学习:

1. 用程序代码把现实世界的问题信息化

2. 用计算机高效的处理这些信息从而创造价值。

2. 数据结构的基本概念

数据:数据是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。(将数据丢给计算机,通过写程序来处理这些数据,进而创造出价值)

数据元素数据元素是数据项的基本单位,通常作为一个整体进行考虑和处理。一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单元。例如:学生记录就是一个数据元素,它是由学号、姓名、性别等数据项组成。

数据对象:数据对象是具有相同性质的数据元素的集合。 

数据结构:数据结构是相互之间存在的一种或多种特定关系的数据元素的集合。在任何问题中,数据元素都不是孤立存在的,它们之间存在某种关系,这种数据元素相互之间的关系称为结构(Structure)。        

数据结构包括三方面内容:逻辑结构、存储结构、数据的运算。数据的逻辑结构和存储结构是密不可分的,算法的设计取决于所采用的逻辑结构,而算法的实现又取决于数据的存储结构。

逻辑结构分为:

        集合结构(各个元素同属于一个集合)

        线性结构(数据元素一对一的关系)

树形结构

图状结构

  

存储结构(又称为物理结构):落实到实处,存储结构就是用计算机去实现这种数据结构。---如何在计算机内部表示数据元素的逻辑关系。常见的存储结构有顺序存储、链式存储、索引存储、散列存储。

顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中。元素间的关系由存储单元的邻接关系来体现。

链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。

索引存储:在存储元素信息的同时,还建立附加的索引表。

散列存储:根据元素的关键字直接计算出元素的存储地址,又称为哈希存储

数据的运算:针对于某种逻辑结构,结合实际需求,定义其基本运算

        ag. 一个榜单:其逻辑结构为线性结构;基本运算有:查找第 i 个数据元素、在第i个位置插入新的数据元素、删除第i个位置的数据元素(增删查改)……

3. 算法和算法评价

3.1 算法的基本概念

        算法(Algorithm)是对特定问题求解步骤的一种描述,他是指令的有限序列,其中每条指令表示一个或多个操作。

程序=数据结构+算法;

也就是说要写一段程序:数据结构在其中的作用是:如何用数据正确的描述该问题,并且存入计算机。

                                        算法的作用是:如何高效的处理这些数据,以解决实际的问题。

3.2 算法的特征

1. 有穷性:一个算法必须在执行有穷步之后结束,且每一步都可在有穷时间内完成。

2. 确定性:算法的每条指令必须有确切的含义,对于相同的输入必须对应相同的输出。

3. 可行性:算法中描述的操作都可以通过已经实现的基本操作执行有限次来实现。

4. 输入:一个算法有零个或多个输入,这些输入取自某个特定的对象的集合。

5. 输出:一个算法有一个或多个输出,这些输出是与输入有着某种特定关系的量。

通常设计一个好的算法需要保证算法:

1. 能够正确的解决求解问题。

2. 帮助人们更好的理解程序,具有良好的可读性。

3. 当算法被非法输入时,算法能够做出反映和处理,而不会盲目的输出结果。

4. 算法执行的效率和时间要合理。

3.3 算法效率的度量

算法效率的度量是通过时间复杂度空间复杂度来描述的。

3.3.1 时间复杂度

频度:一个语句在算法中被重复执行的次数称为该语句的频度比方说:for循环内i++;则i++这条语句循环的次数是for的范围;

算法中所有语句的频度之和记为T(n),它是该算法语句规模n的函数,时间复杂度主要分析T(n)的数量级。算法中基本运算(最深层循环内的语句,也就是循环嵌套最深层次的语句(相当重要))的频度与T(n)同数量级,因此通常采用算法中基本运算的频度f(n)来分析算法的时间复杂度。时间复杂度记为

取f(n)中随n增长最快的项,将其的系数置为1作为时间复杂度的度量。例如:f(n)=an^3+bn^2+cn的时间复杂度为O(n^3);

        也就是说当问题规模比较复杂的时候,可以舍去其阶数较少的部分,只考虑阶数高的部分。(相当重要)

算法的时间复杂度不仅依赖于问题的规模n,也取决于待输入数据的性质(如输入数据元素的初始状态);取决于输入数据的性质具体是什么意思呢?我们看以下程序:


下述程序是在数组A[0……n-1]中,查找某个给定值k的算法

i=n-1;
while(i>=0 && (A[i]!=k))//i>=0并且下角标i对应的元素不为k时执行i--;
{
    i--;
}
return i;

根据上述学习,时间复杂度T(n)和算法中基本运算(最深层次的循环语句)同等级别;本程序中对应的基本算法就是 i--;

i--循环出现的次数,也就是频度取决于第几次能找到k,这显然受初始状态数组A[0……n-1]中n的值影响;

1. 如果数组中没有与k相等的值,那么需要将整个数组循环一次,while循环才会结束,所以此时的时间复杂度为n;

2. 如果A中第 1 个元素是k,那么只需要循环一次就可以找到k,此时的时间复杂度是1;

3. 如果A中最后一个元素是k,那么不需要循环while就可以找到k,因为初始值定义i=n-1,此时的时间复杂度是0;

所以说算法的时间复杂度取决于待输入数据的性质(如输入数据元素的初始状态);

最坏时间复杂度:在最坏的情况下,算法的时间复杂度。

平均时间复杂度:所有可能输入实例在等概率出现的情况下,算法的期望运行时间。

最好时间复杂度:在最好的情况下,算法的时间复杂度。  

一般考虑最坏情况下的时间复杂度,保证算法的运行时间不会比它还要长。

分析时间复杂性时,遵循的两条规则:

加法规则:T(n) = T(n)+T(n)=O((n))+O(g(n)) = O(max(f(n),g(n)) 多项相加,只保留最高阶的项,且系数变为1

乘法规则:T(n) = T(n)×T(n)=O(f(n))×O(g(n)) = O((n)×g(n)  多项相乘,都保留

常见的渐近时间复杂度:

(相当重要)

需要根据该复杂度排序判断加法规则中究竟保留哪个值;因为加法规则定义多项相加,保留的是最高阶的值;需要根据该排序判断究竟谁才是最高阶的值;   口诀:常对幂指阶   -----常数的时间复杂度要小于对数小于幂指数小于指数小于阶乘。

相关练习:

1. 算法的时间复杂度是O(n^2),表明该算法的 执行时间与n^2成正比

2. 以下算法的时间复杂度为:

void fun(int n)
{
	int i=1;
	while(i<=n)
	{
		i=i*2;
	}
}

 显然:该程序的基本语句为i=i*2;时间复杂度就是计算i=i*2循环的次数,通过该程序可以发现,要想离开while循环,只需要i>n即可,分析该循环,每循环一次i=i*2;假设循环t次,每次相当于在原有的基础之上乘个2;循环一次,i=2;循环两次,i=4,循环3次,i=8,所以2的t次方(2^t<=n),所以

拓展: 计算下述程序的时间复杂度?

for(m=1;m<n;m++)
{
	i=1;
	while(i<n)
	{
		i=i*2;
	}
}

根据上述的计算,我们已经计算出while循环中的时间复杂度是log以2为底的n次方,大的for循环m=1;m<n,循环次数为n,所以总的时间复杂度为n倍的log以2为底的n次方。由此可以总结:时间复杂度是有其线性特征的;对于嵌套结构同样适用。

3. 以下算法的时间复杂度为:

void fun(int n)
{
	int i=0;
	while(i*i*i<=n)
	{
		i++;
	}
}

显然,该程序的基本语句为i++,频度也就是i++循环的次数;不妨设i++循环的次数为t,因为i初始值为0,循环t次后i的值为t,所以离开while循环的条件t^3<=n,所以时间复杂度为n开三次方;

4.  计算下述程序的时间复杂度?

for(x=1;i<=n;x++)
{
	for(i=1;i<=n;i++)
	{
		j=1;
		j++;
	}
}

最深层的for循环作为基本语句,显然i=1到i<=n,循环次数为n,外部for循环的循环次数也为n,所以最终的时间复杂度为T(n)=O(n*n);

在此处,如果把外部的for循环改为i<=m,则最终时间复杂度为T(n)=O(m*n);

5. 问题规模参数不只一个的情况 计算时间复杂度

计算Contract的时间复杂度?

void Contract(int N,int M)
{
	int sz=0;
	for(k=0;k<N;k++)
	{
		sz++;
	}
	for(k=0;k<M;k++)
	{
		sz++;
	}
	printf("hehe\n");
	return 0;
}

特别注意:答案是:O(N+M);该时间复杂度的计算不同于循环多次嵌套的情况,Contract接收到的参数N和M在函数体内部的地位是同等的,N和M的阶数是一样的,不知道两者对结果哪个影响大;如果题目写明,N远远大于M,则时间复杂度就是O(N);

其实通过数学极限能够更好的来说明这种情况,在数学极限中,对于高阶无穷小的定义,当复杂的因式不容易化简时,我们通常考虑阶数最大的那个,在无穷比阶的过程中,只有阶数最大的因式在起作用,其余的都会消去;

6. 基本语句的频度在最坏情况下的时间复杂度?

//其中n为正整数,则基本语句的频度在最坏情况下是:
for (i = n - 1; i > 1; i++)
{
	for (j = 1; j < i; j++)
	{
		if (A[j]>A[j + 1])
		{
			A[j]与A[j + 1]对换;
		}
	}
}

该程序中的基本语句显然是: A[j]与A[j + 1]对换;   频度在最坏的情况的下就是if判断语句永远成立的情况。

所以整体是 一个2层for循环嵌套的情况:

第一层for循环的频度是:i从1到n-1;   第二层的频度是:j从1到i;

所以当 i 等于1时,基本语句的频度是0;因为第二层嵌套是从1到1;

当i=2时,基本语句的频度是1,因为第二层嵌套是从1到2;

依次类推,第i=n-1;第二层是从1到n-1;   所以总的时间复杂度是0+1+2+3+4+……n=n(n-1)/2----保留下来高阶就是   T(n)=O(n^2)

7. 计算m++的执行次数?

int m = 0, i, j;
for (i = 1; i <= n; i++)
{
	for (j = 1; j < 2 * i; j++)
	{
		m++;
	}
}

首先: 最外部的for循环i是从1到n的;里面的for循环j是从1到2i的,所以m++首先循环2i次,只不过这个2i次是建立在最外部for循环上的,因此,每当外部for循环一次,内部的2i次就依次变为2*1+2*2+2*3+2*4+……2*n=2(1+2+3+4……+n)=2*(n(n+1)/2)=n(n+1)

8. 下面的程序片段的时间复杂度是?

x = 2;
while (x < n / 2);
x = 2 * x;

 该程序的基本语句为x=2*x;初始条件为x=2;离开循环的条件为x>n/2;每次循环在上一个循环的基础之上乘2,也就说2*2*2*2*2*2*2*2乘到什么时候会大于n/2结束循环,所以2^n>n/2;所以时间复杂度T(n)=O(log以2为底的n次方)

9. 求整数n(n>=0)的阶乘的算法如下,其时间复杂度是:

int fact(int n)
{
	if (n <= 1)
		return 1;
	return n*fact(n - 1);
}

读上述程序:其实就是一个递归。一次递归是一次乘法运算,而值为n的情况下递归嵌套n次。时间复杂度就是O(n)

10. 下列程序段的时间复杂度是:

count = 0;
for (k = 1; k <= n; k = k * 2)
{
	for (j = 1; j <= n; j++)
	{
		count++;
	}
}

该程序的的基本语句是count++;内部循环n次,每次count++;外部循环是1 2 4 8 16 32 一直到k>n时离开循环,所以2^n>n时结束,循环次数log以2为底的n的对数,所用二层嵌套的时间复杂度T(n)=O(nlog以2为底n的对数);

11. 下列程序段的时间复杂度?

x = 0;
while (n >= (x + 1)*(x + 1))
{
	x = x + 1;
}

x从0开始,每次的循环进行加1,循环结束的条件是(x+1)^2>n时结束循环体;假设第k次循环结束程序,那么第k次的判断条件是:(x+1)^2>n;因为x是从0开始的,所以第k次时,x=k-1;也就是(k-1+1)^2>n,所以k等于根号n,也就是时间复杂度等于根号n

总结

1. 当存在for循环嵌套的情况时,如果内外for循环的循环次数,也就是频度都是定值,可以直接相乘得到循环次数,也就是时间复杂度。但是当for嵌套循环有一层的循环次数是不断改变的情况,比如说最普遍的冒泡排序,最外层的for循环是固定的,内部for循环根据外部改变,这个时候不能简单的进行相乘,必须整理清除整个执行过程。

3.3.2 空间复杂度

算法的空间复杂度S(n)定义为该算法所耗费的存储空间。它是问题规模n的函数。记为:

S(n) = O(g(n))

一个程序在执行时除了需要存储空间来存放本身所需的指令、常数、变量和输入数据之外还需要一些对数据进行操作的工作单元和存储一些为实现计算所需信息的辅助空间。若输入数据所占 空间只取决于问题本身,和算法无关,则只需分析除输入和程序之外的额外空间。

原地工作算法所需的辅助空间为常量,即O(1);

ag. 计算下述程序的空间复杂度? 

void test(int n)
{
	int flag[n];
	int i;
}

假设int 占4B,则4+4n+4=4n+8 (int n=4个字节、int i=4个字节、flag[n]数组n个元素共4n个字节);所以空间复杂度就是S(n)=O(n);这里和时间复杂度是一样的,只需要关注存储空间大小与问题规模相关的变量即可。4n+8,关注其高阶,并且高阶系数置1;

ag. 计算下述程序的空间复杂度? 

void test(int n)
{
	int flag[n][n];
	int other[n];
	int i;
}

S(n)=O(n^2)+O(n)+O(1);根据加法规则,保留其中阶数最高的,所以空间复杂度S(n)=O(n^2);

递归调用带来的内存开销空间复杂度=递归调用的深度(递归的层数)* 每一次递归的空间复杂度。

ag. 计算下述递归程序的空间复杂度?

void loveYou(int n)//n是问题规模
{
	int flag[n];
	if(n>1)
	{
		loveYou(n-1);
	}
	printf("I love You %d\n",n);
}
int main()
{
	loveYou(5);
}

显然if判断语句中为递归调用函数,主函数loveYou传参为5,即外部函数void loveYou接收参数5,传给数组flag[5];在函数调用的过程中,第一级调用,数组参数为flag[1];第二级调用,数组参数为flag[2],第三级调用,数组参数为flag[3],第n级调用,数组参数为flag[n],所以最终空间复杂度为1+2+3+4+……n=1/2n^2+1/2n,所以空间复杂度为O(n^2);

总结空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一种度量,空间不比时间,根据程序代码运行的先后顺序,先执行的程序使用完的空间可以供后续程序使用,(一句程序执行完以后它所使用的的那块空间就会被释放),前后所使用的的空间是同一块空间。

也可以计算程序中变量的个数,也就是开辟变量占用空间的大小。

ag. int *pf=(int*)malloc((m+1)*sizeof(int));   表示malloc动态开辟开辟m+1个int型空间,此时该一句程序的空间复杂度就是m+1;

又或者说是:  int i=0;  定义一个变量i;该一句程序的空间复杂度就是O(1);表示一个常数个空间;

for(j=0;j<100;j++);  for循环中定义变量j;该一句程序的空间复杂度就是O(1);表示一个常数个空间;

所以说也可以去计算程序中变量的大小来表示其空间复杂度。

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

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

相关文章

AB数对 码蹄集

题目来源&#xff1a;码蹄集 题目描述&#xff1a; 大致思路&#xff1a; 遍历输入的n个整数&#xff0c;将每个数存入哈希表中&#xff0c;key为数值&#xff0c;value为该数出现的次数。 再次遍历这n个整数&#xff0c;对于每个数x&#xff0c;计算出x-C和xC的值&#xff0…

Go语言基础----Go语言简介

【原文链接】Go语言基础----Go语言简介 一、Go语言简介 Go语言&#xff0c;又称Golang&#xff0c;是Google公司的Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson开发的一种静态强类型、编译型的语言。Go语言语法和C语言接近&#xff0c;但是功能上内存安全&#xff…

【Python】lambda匿名函数

文章目录 前言lambda匿名函数的定义lambda匿名函数的使用使用lambda匿名函数写一个计算器总结 前言 在Python中,可以使用def 关键字定义函数,使用def定义的关键字是有名称的,在调用时可以重复使用.还有一种是使用lambda关键字进行函数的定义,这个方式定义的函数是匿名函数,只能…

51单片机(四)静态数码管和动态数码管显示

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

内存优化-比glibc更快的tcmalloc

TCMalloc 是 Google 开发的内存分配器&#xff0c;在不少项目中都有使用&#xff0c;例如在 Golang 中就使用了类似的算法进行内存分配。它具有现代化内存分配器的基本特征&#xff1a;对抗内存碎片、在多核处理器能够 scale。据称&#xff0c;它的内存分配速度是 glibc2.3 中实…

3d网上渲染平台是怎么渲图的_云渲染流程详解!

题主说的看到许多网友对‘’3d网上渲染平台是怎么渲图的‘’进行提问&#xff0c;瑞云渲染小编也提供自己的小小见解。针对3D网上渲染平台是指什么&#xff0c;实际应该是指云渲染农场。几十年来&#xff0c;随着计算机软硬件不断更迭&#xff0c;图形图像渲染的效果更加清晰、…

信号完整性分析基础知识之传输线和反射(二):阻性负载的反射,源端阻抗,弹跳图

传输线的端接需要考虑三种重要的特殊情况&#xff0c;每种情况中&#xff0c;传输线的特性阻抗均为50Ohm。信号将从源端在这条传输线上传播&#xff0c;并以特定的阻抗端接到达远端。 TIP:在时域中&#xff0c;信号对瞬时阻抗十分敏感&#xff0c;第二区域并不一定是一条传输线…

常见的链表的OJ题

在本次的博客当中&#xff0c;为了巩固关于链表技能的运用&#xff0c;我们先来看一些与链表有关的OJ题。 &#x1f335;反转链表 题目详情如下&#xff1a; 第一道题目从逻辑上看不难&#xff0c;我们只需要将链表进行拆分&#xff0c;将我们下一个节点进行一个类似于头插的操…

【Java 数据结构】Map和Set

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

35岁程序员被裁赔偿27万,公司又涨薪让我回去,前提是退还补偿金,能回吗?

在大多数人眼里&#xff0c;35岁似乎都是一道槛&#xff0c;互联网界一直都有着“程序员是吃青春饭”的说法&#xff0c;。 如果在35岁的时候被裁能获得27万的赔偿&#xff0c;公司又涨薪请你回去上班&#xff0c;你会怎么选&#xff1f; 最近&#xff0c;就有一位朋友在网上…

Linux安装miniconda3

下载Miniconda&#xff08;Python3版本&#xff09; 下载地址&#xff1a;https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 安装Miniconda&#xff08;需要连网&#xff09; &#xff08;1&#xff09;将Miniconda3-latest-Linux-x86_64.sh上传到/o…

研读Rust圣经解析——Rust learn-14(面向对象)

研读Rust圣经解析——Rust learn-14&#xff08;面向对象&#xff09; Rust面向对象对象包含数据和行为封装继承多态 实现面向对象书写最外层逻辑userServiceUser Rust面向对象 在一些定义下&#xff0c;Rust 是面向对象的&#xff1b;在其他定义下&#xff0c;Rust 不是 对象…

算法刷题|300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组

最大递增子序列 题目&#xff1a;给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6…

c++文件操作Ofstream、Ifstream,如何获取文件长度

一、文件光标定位streampos 在读写文件时&#xff0c;有时希望直接跳到文件中的某处开始读写&#xff0c;这就需要先将文件的读写指针指向该处&#xff0c;然后再进行读写。 ifstream 类和 fstream 类有 seekg 成员函数&#xff0c;可以设置文件读指针的位置&#xff1b;ofstr…

OpenGL光照:颜色

知识点归纳 现实世界中有无数种颜色&#xff0c;每一个物体都有它们自己的颜色。我们要做的工作是使用(有限的)数字来模拟真实世界中(无限)的颜色&#xff0c;因此并不是所有的现实世界中的颜色都可以用数字来表示。然而我们依然可以用数字来代表许多种颜色&#xff0c;并且你甚…

autosar

一 autosar简介 AUTOSAR&#xff0c;汽车开放系统架构&#xff08;AUTomotive Open System Architecture&#xff09;是一家致力于制定汽车电子软件标准的联盟。AUTOSAR是由全球汽车制造商、部件供应商及其他电子、半导体和软件系统公司联合建立&#xff0c;各成员保持开发合作…

QT DAY2

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setFixedSize(600,600); //设置固定尺寸this->setWindowTitle("汪玉洁大聪明")…

Hadoop学习笔记(一)Hadoop的组成

1. HDFS NameNode用于记录整个数据的存储情况&#xff0c;具体的数据存储在各个Hadoop节点中&#xff0c;每个Hadoop的节点可以称为DataNode。假设Hadoop1到Hadoop100的机器每个都有1T的容量。那么一共就可以存储100T的数据。 NameNode(nn)&#xff1a;存储文件的元数据&…

位运算【巧妙思路、两种常见题型】

这里介绍两种代码中位运算非常常用的操作 n的二进制表示中第k位数——右移操作 &1 例如说&#xff0c;我们需要计算11的第2位数。 11 (1011)2 我们常规思路就是将其转化为二进制数后&#xff0c;直接观察对应位置的值 这里需要注意的是第k位数指的是从右开始的第k位&a…

Linux shell编程 条件语句

条件测试 test命令 测试表达式是否成立&#xff0c;若成立返回0&#xff0c;否则返回其他数值 格式1: test 条件表达式 格式2: [ 条件表达式 ]文件测试 [ 操作符 文件或者目录 ][ -e 1.txt ]#查看1.txt是否存在&#xff0c;存在返回0 echo $? #查看是上一步命令执行结果 0成…