数据结构——拿捏复杂度

数据结构:一篇拿捏十大排序(超详细版) 十大排序:直接插入排序、希尔排序、直接选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序、基数排序、桶排序,稳定性分析,时间复杂度,空间复杂度。 阅读详情

耕耘 :C、C++、嵌入式技术领域

🔥我的个人主页

❄️个人专栏:《C语言专栏》 《嵌入式专栏》 《数据结构专栏》

✨**不要等待机会,而要创造机会!**✨

📽博主简介:

✨✨一位热爱生活的阳光大男孩.✨✨

前言

本文系统讲解C语言文件操作核心知识,涵盖文件概念、流与文件指针机制,重点解析fopen、fclose及错误处理,结合fseek、ftell实现随机读写,并深入剖析缓冲区原理与fflush刷新机制,强调打开后判空、关闭置空等最佳实践,为高效、安全的文件操作提供完整技术框架。

1.数据结构前⾔

1.1 数据结构

数据结构是计算机存储、组织数据的⽅式,指相互之间存在⼀种或多种特定关系的数据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤,所以我们要学各式各样的数据结构,
如:线性表、树、图、哈希等

1.2 算法

算法就是定义良好的计算过程,他取⼀个或⼀组的值为输⼊,并产⽣出⼀个或⼀组值作为输出。简单来说算法就是⼀系列的计算步骤,⽤来将输⼊数据转化成输出结果。

2. 算法效率

思路:轮转数组,循环K次将数组所有元素向后移动⼀位
1、用循环的嵌套

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;
	}
}
int main()
{
	int arr[7] = { 1,2,3,4,5,6,7 };
	for (int i = 0; i < 7; i++)//轮转前
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	rotate(arr, 7, 3);//轮转3次
	for (int i = 0; i < 7; i++)//轮转后
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述

2.1 复杂度的概念

算法在编写成可执⾏程序后,运⾏时需要耗费时间资源和空间(内存)资源 。因此衡量⼀个算法的好坏,⼀般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
**时间复杂度主要衡量⼀个算法的运⾏快慢,⽽空间复杂度主要衡量⼀个算法运⾏所需要的额外空间。**在计算机发展的早期,计算机的存储容量很⼩。所以对空间复杂度很是在乎。但是经过计算机⾏业的迅速发展,计算机的存储容量已经达到了很⾼的程度。所以我们如今已经不需要再特别关注⼀个算法的空间复杂度。

3. 时间复杂度

定义:在计算机科学中,算法的时间复杂度是⼀个函数式T(N),它定量描述了该算法的运⾏时间。时间复杂度是衡量程序的时间效率。这个T(N)函数式计算了程序的执⾏次数。

程序执行时间 = 二进制指令运行时间 * 执行次数
执⾏次数就可以代表程序时间效率的优劣。⽐如解决⼀个问题的算法a程序T(N) = N,算法b程序T(N) = N^2,那么算法a的效率⼀定优于算法b。

注意:时间复杂度描述的是一个程序运动的趋势,而不是他整体运行的时间。

案例:

// 请计算⼀下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) = N2+ 2 ∗ N + 10
通过对N取值分析,对结果影响最⼤的⼀项是 N2

实际中我们计算时间复杂度时,计算的也不是程序的精确的执⾏次数,上⾯我
们已经看到了当N不断变⼤时常数和低阶项对结果的影响很⼩,所以我们只需要计算程序能代表增⻓量级的⼤概执⾏次数,复杂度的表⽰通常使⽤⼤O的渐进表⽰法。

3.1 ⼤O的渐进表⽰法

⼤O符号(Big O notation):是⽤于描述函数渐进⾏为的数学符号
1.时间复杂度函数式T(N)中,只保留最⾼阶项,去掉那些低阶项,因为当N不断变⼤时,低阶项对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
2.如果最⾼阶项存在且不是1,则去除这个项⽬的常数系数,因为当N不断变⼤,这个系数对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
3.T(N)中如果没有N相关的项⽬,只有常数项,⽤常数1取代所有加法常数。

3.2 时间复杂度计算⽰例

3.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执⾏的基本操作次数:T (N) = 2N + 10
根据推导规则第3条得出Func2的时间复杂度为: O(N) **

3.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执⾏的基本操作次数:T (N) = M + N (因为M,N都不知道次数,所以他们可能相等,则可能T(N)=2N或2M)
因此:Func2的时间复杂度为: O(N) 或 O(M)

3.2.3 ⽰例3

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

T (N) = 100
根据推导规则第1条得出Func2的时间复杂度为: O(1)

3.2.4 ⽰例4

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

strchr执⾏的基本操作次数:
(1)若要查找的字符在字符串第⼀个位置,则:T (N) = 1
(2)若要查找的字符在字符串最后的⼀个位置,则:T (N) = N
(3)若要查找的字符在字符串中间位置,则:T (N) =N/2

因此:strchr的时间复杂度分为:
最好情况: O(1)
最坏情况: O(N)
平均情况: O(N)

总结
通过上⾯我们会发现,有些算法的时间复杂度存在最好、平均和最坏情况。
最坏情况:任意输⼊规模的最⼤运⾏次数(上界)
平均情况:任意输⼊规模的期望运⾏次数
最好情况:任意输⼊规模的最⼩运⾏次数(下界)
⼤O的渐进表⽰法在实际中⼀般情况关注的是算法的上界,也就是最坏运⾏情况。

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

(1)若数组有序,则:T (N) = N
(2)若数组有序且为降序,则:T (N) =2N ∗ (N + 1)
(3)若要查找的字符在字符串中间位置,则:
因此:BubbleSort的时间复杂度取最差情况为: O(N2 )

3.2.6 ⽰例6

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

当n=2时,执⾏次数为1
当n=4时,执⾏次数为2
当n=16时,执⾏次数为4
假设执⾏次数为 x ,则 2x= n
因此执⾏次数: x = log n
因此:func5的时间复杂度取最差情况为:O(log2 n)

注意课件中和书籍中 log2 n 、 log n 、 lg n 的表⽰
当n接近⽆穷⼤时,底数的⼤⼩对结果影响不⼤。因此,⼀般情况下不管底数是多少都可以省略不写,即可以表⽰为 log n
不同书籍的表⽰⽅式不同,以上写法差别不⼤,我们建议使⽤ log n

3.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次递归调⽤Fac函数
因此:阶乘递归的时间复杂度为: O(n)

4. 空间复杂度

空间复杂度也是⼀个数学表达式,是对⼀个算法在运⾏过程中因为算法的需要额外临时开辟的空间。空间复杂度不是程序占⽤了多少bytes的空间,因为常规情况每个对象⼤⼩差异不会很⼤,所以空间复杂度算的是变量的个数。

空间复杂度计算规则基本跟实践复杂度类似,也使⽤⼤O渐进表⽰法。

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

4.1 空间复杂度计算⽰例

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

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

4.1.2 ⽰例2

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

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

5. 常⻅复杂度对⽐

在这里插入图片描述
在这里插入图片描述

6. 复杂度算法题

6.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];
			}
		 nums[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];
	}
	for (int i = 0; i < numsSize; ++i)
	{
		nums[i] = newArr[i];
	}
}

思路3:
空间复杂度 O(1)
• 前n-k个逆置:4 3 2 15 6 7
• 后k个逆置 :4 3 2 17 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--;
	}
}
void 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);
}

结语

愿你收获满满,点赞、收藏、转发三连不断,好运常伴!
完.

双模音频解码器设计:USB+蓝牙LDAC平衡输出方案 音频解码器是数字音频系统中实现数模转换的核心组件,其性能直接决定音质保真度与系统延迟。基于高集成SoC的解码方案通过统一时钟域与片内DSP处理,有效抑制Jitter并简化硬件拓扑;结合全平衡模拟输出架构,可显著提升共模抑制比(CMRR)与信噪比(SNR)。该技术路径广泛应用于便携式Hi-Res音频设备、USB-C小尾巴DAC及蓝牙高解析耳机放大器等场景。本文围绕ES9038Q2M双模SoC展开,详解其在LDAC协议支持、UAC2.0兼容性及平衡电流型DAC输出方面的工程实现,为高性能、可量产音频硬件开发提供 阅读详情

相关推荐

海康/大华设备如何接入GB28181平台?最新2022版互联避坑指南

本文详细解析了海康/大华设备接入GB28181平台的最新2022版互联避坑指南,涵盖参数配置、实时视音频点播协议交互及典型故障排查。重点介绍了GB/T28181-2022标准下的设备编码规范、SIP服务器配置及媒体流保活机制,帮助工程团队高效解决设备离线和媒体流中断等问题。

weixin_30733003的博客 685

数据结构(长期更新)第1讲:算法复杂度

本文介绍了数据结构课程中算法复杂度的基本概念,重点讲解了时间复杂度的计算方法。主要内容包括:1)数据结构与算法的基本定义;2)算法效率的衡量标准(时间/空间复杂度);3)大O渐进表示法的推导规则;4)多种时间复杂度计算实例(线性、平方、对数、递归等)。文章强调时间复杂度分析的重要性,并详细说明了不同情况下的复杂度评估方法,特别是最坏情况下的复杂度分析。通过数学公式和实例演示了如何将具体执行次数转换为大O表示形式。

2401_88066253的博客 1244

Homebrew 必备软件清单:我的 macOS 开发环境搭建全记录

本文详细介绍了如何使用Homebrew在macOS上搭建高效的开发环境,包括基础配置、核心开发工具链、高效编码工具集等30个必备工具。通过Homebrew这一macOS缺失的包管理器,开发者可以一键安装命令行工具和GUI应用,大幅提升工作效率。文章还提供了日常维护命令和疑难问题解决方案,适合从新手到资深开发者的所有用户。

weixin_28836507的博客 353

【数据结构】从零拿捏复杂度(时间复杂度 + 空间复杂度)

1. 时间复杂度通过大O渐进表示法,保留最高阶项并忽略常数系数,常见类型包括O(1)、O(N)、O(N²)、O(logn)和O(2^N)。 2. 空间复杂度同样使用大O表示法,主要关注算法运行时的额外空间占用。空间复杂度在当前计算环境中重要性较低。

2401_88162402的博客 1013

MarkDown简介(深度解析MarkDown用法)

欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和图片上传 LaTex数学公式 UML序列图和流程图 离线写博客 导入导出Markdown文件 丰富的快捷键 快捷键 加粗 Ctrl + B 斜体 Ctrl + I...

酷酷的旭 7340

开放平台 - 互动玩法演进之路

随着直播业务和用户规模日益壮大,如何丰富直播间内容、增强直播间内用户互动效果,提升营收数据变得更加关键。

bilibili_TC的博客 1735

拿捏数据结构-top_k问题

在完全二叉树中,叶子节点不包含任何子节点,因此不需要进行调整。

Jason_from_China的博客 2023

NSX-T 4.1部署搭建

NSX-T4.1 部署搭建

weixin_58250887的博客 564

为了拿捏 Redis 数据结构,我画了 40 张图(完整版)

大家好,我是小林。 Redis 为什么那么快? 除了它是内存数据库,使得所有的操作都在内存上进行之外,还有一个重要因素,它实现的数据结构,使得我们对数据进行增删查改操作时,Redis 能高效的处理。 因此,这次我们就来好好聊一下 Redis 数据结构,这个在面试中太常问了。 注意,Redis 数据结构并不是指 String(字符串)对象、List(列表)对象、Hash(哈希)对象、Set(集合)对象和 Zset(有序集合)对象,因为这些是 Redis 键值对中值的数据类型,也就是数据的保存形式,这些对象的底

小林coding 1万+

电阻抗成像肺功能测试数据分析与直方图生成

本文介绍了基于MATLAB的电阻抗成像(EIT)肺功能测试数据分析方法。研究针对32×32像素的EIT图像序列,通过三步算法处理:首先计算特定时间点与谷值图像的差值,然后分析1秒后峰值与1秒值的差值,最终计算比值矩阵并处理异常值。系统展示了数据预处理、关键时间点检测、比值计算及统计分析的全流程,包括生成概率分布直方图和空间分布图等可视化方法。该分析为无创肺功能评估提供量化指标,可应用于临床呼吸监测和疾病诊断。

huanghm88的专栏 197

基于STM32+OneNet设计的物联网智慧路灯(100)

本次设计的智能路灯采用STM32芯片和OneNet物联网平台进行连接,开发出支持太阳能充电锂电池供电、GPS定位上传位置信息、手动/自动控制路灯开关、环境光照调节、红外传感器检测周围人员、空气质量监测等多项功能。同时,当路灯发生异常情况时,系统可以自动发送邮件或短信到指定邮箱或电话。

7722

为了拿捏后端打工人都要会的 Redis 数据结构,我画了 20 张图

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

2401_85124329的博客 689

Python无法拒绝的表白界面(完整代码)

Python制作一个无法拒绝的表白界面的完整代码。

AIGC 5万+

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值基准值的注释:在的过程中,每一次我们要取一个元素作为,以这个数字来将序列划分为两部分。在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。

酷酷的崽的博客 1474

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

人人都要会的 Redis 数据结构,为了拿捏它 , 我画了 20 张图

这样在修改字符串的时候,可以通过 alloc - len 计算 出剩余的空间大小,然后用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区益处的问题。。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。。

2401_84048935的博客 594

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

20张图带你彻底拿捏Redis数据结构 !

Redis 为什么那么快? 除了它是内存数据库,使得所有的操作都在内存上进行之外,还有一个重要因素,它实现的数据结构,使得我们对数据进行增删查改操作时,Redis 能高效的处理。

wdj_yyds的博客 731

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

这篇带你彻底拿捏Redis数据结构 !

Redis 为什么那么快? 除了它是内存数据库,使得所有的操作都在内存上进行之外,还有一个重要因素,它实现的数据结构,使得我们对数据进行增删查改操作时,Redis 能高效的处理。 因此,这次我们就来好好聊一下 Redis 数据结构,这个在面试中太常问了。

wdj_yyds的博客 329

为了拿捏 Redis 数据结构,我画了 40 张图

Redis 为什么那么快?除了它是内存数据库,使得所有的操作都在内存上进行之外,还有一个重要因素,它实现的数据结构,使得我们对数据进行增删查改操作时,Redis 能高效的处理。因此,这次我...

架构师小秘圈 320

拿捏小算法——数组和链表

数组、链表、时间、空间复杂度

eci__的博客 121

程序员必须拿捏哪些算法?

为了让你对数据结构和算法能有个全面的认识,我画了一张图,里面几乎涵盖了所有数据结构和算法书籍中都会讲到的知识点。 这里面有10个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树;10个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法。 掌握了这些基础的数据结构和算法,再学更加复杂的数据结构和算法,就会非常容易、非常快。 小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。我有一些面.

m0_67698950的博客 264

【数据结构与算法-Day 8】手把手带你拿捏单向链表:增、删、改核心操作详解

本文将作为【单向链表核心操作】的上篇,聚焦于链表中最基础、最核心的“写”操作:**增加(插入)**、**删除**和**修改**节点。我们将通过图文并茂的方式,深入剖析每种操作在不同场景下的原理和实现细节,并提供高质量、附带注释的代码示例。本文旨在帮助你彻底掌握链表的指针操作精髓,为你后续学习更复杂的算法打下坚实的基础。

吴师兄大模型的博客 1274
上一篇: C语言——预处理详解
下一篇: 数据结构——顺序表细致讲解
Chen_LSN
博客等级 码龄1年 386粉丝 21原创
评论 123
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chen_LSN

赏金充咖啡,熬夜调试代码不打烊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值