数据结构——复杂度讲解

news2024/12/24 8:46:52

已经太久没用更新了,由于各种原因,导致很久没用更新了,但是停更期间我也是一直在很努力的学习与复习之前学过的知识,读了两本C语言的数据,初学者也是可以看的,推荐给大家,如果需要pdf,可以私信我

第一本:《C陷阱与缺陷》

第二本:《C语言深度剖析》

回归正题,今天开始正式学习数据结构,我将带领大家由浅入深的学习,不用害怕,因为我会把我认为比较难懂的知识学习好几遍,然后再给大家写出来。今天所讲的算法复杂度难度还好,不是很难,大家放心,不用担心学不会。

正式开始学习:

目录

1、算法复杂度

2、如何学好数据结构

3、算法效率

3.1、什么是算法效率?

3.2、复杂度的计算

4、时间复杂度

4.1、大O的渐进表示法

4.2时间复杂度计算示例

4.2.1、示例1:

4.2.2、示例2:

4.2.3、示例3:

4.2.4、示例4:

4.2.5、示例5:

4.2.6、示例6:

4.2.7、示例7:

5、空间复杂度

5.1 空间复杂度计算⽰例

5.1.1 ⽰例1

5.1.2 ⽰例2

6、常⻅复杂度对⽐

7、复杂度算法题

7.1 旋转数组

思路1

思路2

思路3


1、算法复杂度

数据结构与算法介绍:

数据结构:数据结构的内存中存储数据的格式,指的是相互之间存在一种或多种特定关系的数据元素集合与数据元素之间的集合,一种数据结构不会适用于全部的用途,所以我们要学习数据结构,数据结构包括:数组、链表、线性表、哈希值等等。

算法:算法是指数据输入与输出时,在底层中是如何实现,有的算法对于程序运行快,有的慢,但实现的功能都是一样的。

总结:数据结构管理在内存中是如何放置的,算法是如何在内存中计算的

数据结构与算法的概念是不是特别的简单,所以大家不要听到数据结构这门课的名字就害怕自己学不会,我们把这些拗口的语言给打回原形,让它们看起来也没用这么高大尚。

2、如何学好数据结构

有小伙伴问了,我们应该如何学习数据结构呢,我这里给出两点的建议:

秘诀1:死磕代码!!!

秘诀2:画图画图画图+思考!!!

3、算法效率

3.1、什么是算法效率?

算法效率就是我们所写的代码运行的速度快慢,比如:有两个人一起学习,学习的时间地点都是一样的,第一个人学的很快,第二个人学的很慢,很明显,第一个人学习效率高,第二个人学习效率低,这就是因为学习方法不一样导致的,虽然这两个人都可以学会相同的知识。

我们所写的代码也是如此,两个人写同一道算法题目时候,一个人写的代码运行效率快,一个人写的代码运行效率慢,但是最终实现的结构都是相同的。

如何衡量⼀个算法的好坏呢?

案例:旋转数组https://leetcode.cn/problems/rotate-array/description/

思路:循环K次将数组所有元素向后移动⼀位

void rotate(int* nums, int numsSize, int k) {
    while (k--) {
        int end = nums[numsSize - 1];
        for (int i = numsSize - 1; i > 0; i--) {
            nums[i] = nums[i - 1];
        }
        nums[0] = end;
    }
}

在我们刚学完C语言的同学们,肯定第一时间想到的是使用两个for循环进行嵌套到达交换,我也是第一时间想到的是两个for循环进行嵌套,那么这个写法到底对不对呢?有没有通过测试?

我们看见了,这个题目一共会验证38个输入数据,我们通过了0-36个输入数据,在进行第37个输入数据时候,显示超出计算时间,显然,我们算法实现的功能是没用问题的,主要的问题在于在进行很大数计算的时候,我们所写的代码运行时间超出了题目给定的时间限制,这时候我们就需要优化我们的算法了。

3.2、复杂度的计算

一般我们衡量一个算法的好坏是从时间与空间两个维度进行衡量的,分别成为时间复杂度与空间复杂度

时间复杂度是一个衡量算法运行快慢的

空间复杂度是衡量是指一个算法在运行时所需要的额外空间。

4、时间复杂度

定义:在计算机科学中,算法的时间复杂度是一个函数T(N),他定量描述了算法的运行时间,时间复杂度是该程序运行的时间快慢,那么我们为什么不直接计算程序的运行时间呢?

  1. 在相同的机器上,相同的代码分别放入新编译器与老编译器中,程序所执行的运行时间快慢是不一样的。
  2. 不同配置的电脑,在同一款编译器运行相同的代码,由于电脑的配置不一样,会导致计算出来的程序运行的速度快慢不一样
  3. 测试代码运行的时候,需要在代码完成以后才能进行测试,会耽误我们写代码。

总结:所以我们通过一个T(N)的算法模型,就可以大致判断代码的运行效率,可以大大提高我们代码的算法与书写效率。

案例:

前情提要:在我们计算T(N)的时候,我们所需要计算的是程序中最坏的情况(最大运行的时间)

// 请计算⼀下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;
	}
}

我们如何计算T(N)呢?

我们需要找到程序中导致程序运行速度的主要地方,但是我们这一次分析程序全部的运行次数,让大家方便理解。

  1. 在一个for循环中又嵌套一个for循环,而且这两个循环的判断语句都是i,j<N,所以我们最坏的运行情况是外循环执行N次,内循环执行N次。N*N
  2. 在第二个循环中,判断语句是2N,这是很好看出来的。2N
  3. 第三个是M=10,占用十次运算。10

得出来我们的T(N):N^2+2N+10.

如果N=10;

10^2+20+10=130

如果N=100;

100^2+200+10=10210

如果N=100000。

100000^2+200000+10= 10000200010

通过上述三个给N的数值,我们可以看出来,N^2对该代码的运行效率所产生的运行时间快慢影响是最大的,从而我们可以写出来该程序的时间复杂度是T(N^2)。

我们无需精确的计算程序每一条语句的计算时间,这是无意义的,因为对于计算机而言,执行的速度是很快的,我们人类认为很大的数字,计算机一下子就可以计算完毕。

上面我们看见当N不断变大时常数和低阶项对结果影响很小,所以我们只需要计算程序能代表最大增长量级的大概执行次数,复杂度的表示通常使用大O的渐进法来表示。

4.1、大O的渐进表示法

函数的渐近行为是描述算法的时间复杂度与空间复杂度随着输入规模增加时的变化趋势,他帮助我们理解在处理非常大的数据时,算法的性能表现

大O符号:用于描述函数渐进行为的数学符号:

  1. 在时间复杂度T(N)中,只保留最高项,忽略低阶项,因为随着输入规模越来越大时,低阶项的影响越来越小
  2. 如果最高项存在且不是1,如:2N,那么就忽略常数项,直接写N。因为当N不断变大的时候,常数项的影响会越来越小。还用2N这个例子:当N位无穷大的时候,如果2无穷大也等于无穷大,所以我可以直接忽略
  3. 如果T(N)中没用N,是一个常数的话,那么我们就直接写成O(1),因为对于计算机来说,计算机运行的速度是非常快的,我们人类觉得大的数字,计算机很快就能运行完。

介绍完大O以后,大家还是不理解,那么我就先说出我学习这部分的疑惑,给大家解释一下:

1、N是什么?N是我们输入的数据,可以很大可以很小,比如我们第一个代码例子中,输入数据小的时候,可以通过测试,输入数据过大的时候,就会超时运行,所以N看作是一个变量

2、如果是O(10000000000000)那还要写成O(1)吗?是的,是需要写成O(1),还是那句话,对于我们人来说”10000000000000“这个数很大,但是对于机器来说,运行速度很快就可以完成运行。如果这里大家实在不理解,后续我会给出一张运行效率的图标,大家可以先试着背一下,后续再慢慢理解也是可以的,如果实在不理解,可以私信问我。

4.2时间复杂度计算示例

4.2.1、示例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);
}

Func2函数中的时间复杂度是2*N+10,在上述所说的大O符号表示法中,在2N中常数项2对代码运行效率影响是比较低的,同时常数10对代码影响也是较低,所以取对代码运行效率最大的,也就是我们刚刚所说的大O。

所以Func2函数中时间复杂度可以使用O(N)来表示。

4.2.2、示例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);
}

在Func3函数中,我们的输入变量一共有两个,在Func3中有三种大O的表示方法:

1、N>> M那么写成O(N)

2、M>>N那么写成O(N)

3、N≈M那么写成O(N+M)

注意:这不是循环嵌套,如果是循环嵌套那么写成O(N*M)。

4.2.3、示例3:

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

在Func4函数中,很明显的一点就是,判断for循环的条件是K<100,Func4函数传进来的形参N并不会在运行这个函数中用到,在这个函数中时间复杂度是O(100),在上面所讲的大O的表示形式中第三条可以得到,Func3函数的时间复杂度为O(1)。

4.2.4、示例4:

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

strchr函数是为了实现输入一个字符(character),在字符串(str)中查找我们所输入的字符是否在str中,如果存在那么就return p_begin;,否则就return NULL;

在这个函数中查找字符时候有三种时间复杂度T(N).

1、如果我们所需要查找的字符在字符串开头就找到了,那么时间复杂度表示T(N)=1;

2、如果我们所需要查找的字符在字符串中间找到了,那么时间复杂度表示T(N)=2/N;

3、如果我们所需要查找的字符在字符串末尾才找到了,那么时间复杂度表示T(N)=N;

我们可以得到大O分别为:O(1),O(N),O(N)

我们找出了这三种字符在字符串中查找出现的位置,我们上述所说,我们函数渐进式影响运行效率最大的数据,那么我们就可以很明显的得到为O(N).

4.2.5、示例5:

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

上述代码是冒泡排序,当外层循环一次,内存循环实现数据交换,这个函数并不是像两个一样由一个判断语句控制,所以分析这个代码的时候,要小心点。

每经历一次循环,end就会变小,交换的次数就会变少,所以这是一个等差数列,通过等差数列的公式我们可以得到,时间复杂度为O(N^2)。

4.2.6、示例6:

// 计算Func4的时间复杂度?
void func5(int n)
{
	int cnt = 1;
	while (cnt < n)
	{
		cnt *= 2;
	}
}

Func5函数中,循环的判断语句是cnt<n。

我们假设n=10,我们循环的时候,第三次循环cnt=8还是cnt<n,当第四次循环cnt直接等于16了,此时cnt>n停止循环。

此时我们如何写时间复杂度呢?

可以使用log来表示   logn^16=4,此时系统就可以知道,我们执行了四次循环,时间复杂度我们就可以直接描述为O(logn),底数写不写都一样,计算机执行速度很快,对于速度影响不大。

4.2.7、示例7:

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

递归的时间复杂度是由他递归调用的次数和每次调用计算开销来决定的,我们一般分析最坏情况下的递归深度和工作量

在这个函数中调用fac的时间复杂度是O(1),但是fac函数存在n次调用的情况,所以是O(N)。

5、空间复杂度

空间复杂度是在该函数中,除去计算机先前已经开辟好的空间,在该函数中额外需要再次开辟的空间。

5.1 空间复杂度计算⽰例

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

最为简单的理解计算,看系统在这段函数中开辟了几个栈帧,这个函数中开辟的栈帧为3个

因此最大空间复杂度为O(1)

5.1.2 ⽰例2

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

在每次进行递归的时候,都会创建新的栈帧(额外的空间)

空间复杂度为:O(N)

6、常⻅复杂度对⽐

曲线越平缓性能越好,复杂度越小。

7、复杂度算法题

7.1 旋转数组

回到我们刚开始讲解的旋转数组题目,我们大致明白要怎么样减小时间复杂度了,如果能让我们之前所写的第一个代码的时间缩小一级,那会不会通过呢?

思路1

时间复杂度  O(n2)
循环K次将数组所有元素向后移动⼀位(代码不通过)
void rotate(int* nums, int numsSize, int k) {
	while (k--)
	{
		int end = nums[numsSize - 1];
		for (int i = numsSize - 1; i > 0; i--)
		{
			nums[i] = nums[i - 1];
		} n
			ums[0] = end;
	}
}

思路2

空间复杂度  O(n)

申请新数组空间,先将后k个数据放到新数组中,再将剩下的数据挪到新数组中。

void rotate(int* nums, int numsSize, int k)
{
	int newArr[numsSize];
	for (int i = 0; i < numsSize; ++i)
	{
		newArr[(i + k) % numsSize] = nums[i];
	} f
		or (int i = 0; i < numsSize; ++i)
	{
		nums[i] = newArr[i];
	}
}

思路3

空间复杂度  O(1)  
• 前n - k个逆置:4 3 2 1 5 6 7
• 后k个逆置    :4 3 2 1 7 6 5
• 整体逆置     :5 6 7 1 2 3 4
void reverse(int* nums, int begin, int end)
{
	while (begin < end) {
		int tmp = nums[begin];
		nums[begin] = nums[end];
		nums[end] = tmp;
		begin++;
		end--;
	}
} v
oid rotate(int* nums, int numsSize, int k)
{
	k = k % numsSize;
	reverse(nums, 0, numsSize - k - 1);
	reverse(nums, numsSize - k, numsSize - 1);
	reverse(nums, 0, numsSize - 1);
}

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

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

相关文章

SAP TR传输工具

实现效果 不用释放请求&#xff0c;可以把对方对象从DEV直接释放到目标系统中 表对象设计 表结构设计 1. ZSAB0007 Develop toolset: current working objects 2. Structure&#xff1a;ZSAB0008 发版计划请求列表 3. ZSAB0022 Development objects - object search r…

Bootstrap布局实例(偏移列)

偏移是一个用于更专业的布局的有用功能。它们可用来给列腾出更多的空间。例如&#xff0c;.col-xs-* 类不支持偏移&#xff0c;但是它们可以简单地通过使用一个空的单元格来实现该效果。 为了在大屏幕显示器上使用偏移&#xff0c;请使用 .col-md-offset-* 类。这些类会把一个…

Vue3: setup语法糖

一. setup语法糖 在 Vue 3 中&#xff0c;setup 语法糖是一种简化组件内部状态和方法管理的特性。它允许你将组件的逻辑直接编写在组件的定义中&#xff0c;而不是像 Vue 2 那样需要在 methods 和 data 属性中管理。setup 语法糖基于 ES6 的类的静态方法&#xff0c;允许你更灵…

改进大语言模型的最全方法!

这是一篇关于适应开源大语言模型&#xff08;LLMs&#xff09;的三部系列博客的第一篇。本文探讨将LLM适应领域数据的各种方法。 第二部分讨论如何确定微调&#xff08;fine-tuning&#xff09;是否适合您的用例。第三部分探讨策划良好训练数据集的一些经验法则。 0 引言 大…

NX二次开发—批量导出点工具

在NX上进行二次开发&#xff0c;设计一个UI界面&#xff0c;将选择的点导出 在NX上&#xff0c;进行UI样式编辑器 添加选择对象&#xff0c;修改标题&#xff0c;设置为多选 创建一个组&#xff0c;添加枚举&#xff0c;设置标题和枚举内容&#xff0c;不显示枚举标题LabelVis…

云微客AI文案编写,有手就能出“爆款”

​现如今新媒体时代&#xff0c;短视频平台已经成为了企业品牌宣传的重要战场&#xff0c;那么如何利用短视频平台进行品牌宣传、制作爆款视频就成为了各大商企需要解决的难题。由此&#xff0c;不得不提到云微客短视频矩阵系统&#xff0c;一键助力商企品牌轻松打造爆款内容。…

python学习第九节:爬虫实战-抓取地址库

python学习第九节&#xff1a;爬虫实战-抓取地址库 话不多说&#xff0c;直接上代码&#xff1b;下面的代码是从统计局抓取地址库并保存为json格式和excel格式。大家拿到代码直接运行即可。 #codingutf-8 #加入上面这行代码表示可以在下面代码中包含中文 import bs4 #网页解析…

wopop靶场漏洞挖掘练习

一、sql注入漏洞 1.在搜索框输入-1 union select 1,2,3# 可以看到页面有回显 2.查询数据库名 -1 union select 1,2,database()# 3.通过查询admin表的数据可以进行登录后台 -1 union select 1,2,group_concat(user_name,user_pass) from admin# 二、文件上传漏洞 1.登录后台…

李龙受邀参加济南高新区“质量月”能力提升活动,并做专题培训

9月11日&#xff0c;济南高新区在山东省知识产权公共服务平台举行2024年“质量月”启动仪式暨质量管理能力提升系列活动。安畅检测首席技术专家李龙先生出席了“质量月”启动仪式 &#xff0c;并为到场企业代表就信创产业相关知识做了专题培训。 济南市市场监督管理局党组成员王…

101.游戏安全项目-创建人物对象结构

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;100.游戏安全项目-不可见数据的搜索 以 98.游戏的启动与多开-分析与实现多开器 它的代码…

7.sklearn-逻辑回归、精确率和召回率、ROC曲线和AUC指标

文章目录 环境配置&#xff08;必看&#xff09;头文件引用1.逻辑回归1.1 API介绍1.2 代码实现1.3 运行结果 2.分类评估方法2.1 精确率(Precision)2.2 召回率(Recall)2.3 F1-score2.4 分类评估报告api2.5 代码工程2.6 运行结果 3.ROC曲线与AUC指标3.1 TPR和FPR3.2 ROC曲线3.3 A…

2024年宠物空气净化器选购攻略?哪款最值得买

表妹在去年刚上大学就养了一只爱掉毛的银渐层&#xff0c;宿舍矛盾不断激化&#xff0c;甚至一度产生了退学的念头。 究其原因&#xff0c;主要是她觉得刚进大学太孤独和身边的人都不太熟&#xff0c;所以就不想聊天&#xff0c;为了缓解这种孤独养了一只银渐层&#xff0c;有…

用 nextjs 创建 Node+React Demo

1、环境准备 1、安装Node 访问Node官网下载对应Node版本&#xff1a;Node官网&#xff0c;安装成功后通过命令查看当前node版本 node -v2、安装Node版本管理工具nvm 如果nvm install 安装node失败&#xff0c;一般是网络问题&#xff0c;可以用手机热点或者翻墙 # 安装nvm c…

ssm“健康早知道”微信小程序 LW PPT源码调试讲解

第二章开发技术与环境配置 以Java语言为开发工具&#xff0c;利用了当前先进的SSM框架&#xff0c;以MyEclipse10为系统开发工具&#xff0c;MySQL为后台数据库&#xff0c;开发的一个“健康早知道”微信小程序。 2.1 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2…

通义灵码获得国产 AI 编码工具最高成绩丨阿里云云原生 8 月产品月报

云原生月度动态 云原生是企业数字创新的最短路径。 《阿里云云原生每月动态》&#xff0c;从趋势热点、产品新功能、服务客户、开源与开发者动态等方面&#xff0c;为企业提供数字化的路径与指南。 趋势热点 &#x1f947; Gartner 首次发布 AI 代码助手魔力象限&#xff0…

数据链路层/ARP协议

当一个报文需要从一个主机转发到另一个主机的时候&#xff0c;表面上是 IP 报文的跨网络转发&#xff0c;但也并不是直接就将数据报转发到对应的主机了&#xff0c;而是从网络层更下面的数据链路层一跳一跳的转发到下一个链路层&#xff0c;数据链路层实现的是到达短距离目的地…

2024年让你的营销机构省时的18款AI工具

提高效率是经营一家盈利的营销机构的关键。利用AI工具不仅可以提升效率&#xff0c;还能够保持甚至增加团队的工作量和工作质量。 这些都是非常理想的结果。然而&#xff0c;随着越来越多的AI工具问世&#xff0c;从哪里入手呢&#xff1f; 在这里&#xff0c;你会找到一份简…

公司小、资源少?别慌!5招让你有效开展测试工作

在经济环境下行的当下&#xff0c;很多测试人员被迫去一些小公司先渡过难关&#xff0c;但是去小公司做测试往往都会遇到很多问题&#xff0c;除了要给一些开发人员普及测试的概念和流程&#xff0c;而且还要从0-1准备测试资源&#xff0c;因为我就是这么过来的&#xff0c;所以…

【6大设计原则】解锁代码的灵活性:深入解析开闭原则的代码实例与应用

1.引言 在软件开发中&#xff0c;设计模式是解决常见问题的经过验证的解决方案。设计模式不仅提供了一种可复用的设计思路&#xff0c;还有助于提高软件的质量和可维护性。设计模式的六大原则是指导我们进行软件设计的基石&#xff0c;其中开闭原则&#xff08;Open/Closed Pr…

携手科大讯飞丨云衔科技为企业提供全栈AI技术解决方案

作为智能时代的核心驱动力&#xff0c;人工智能不仅重塑了传统行业的面貌&#xff0c;更开辟了全新的经济增长点。科大讯飞以其深厚的技术底蕴和创新能力&#xff0c;持续引领着人工智能领域的发展潮流。云衔科技作为科大讯飞开放平台的AI技术产品线合作伙伴代理商&#xff0c;…