【数据结构入坑指南(一)】--《实战:暴力解VS最优解——一道轮转数组题引发的复杂度「血案」》

【数据结构入坑指南(一)】--《实战:暴力解VS最优解——一道轮转数组题引发的复杂度「血案」》

🔥@晨非辰Tong:个人主页 

👀专栏:《C语言》《数据结构与算法》

💪学习阶段:C语言方向初学者

⏳“人理解迭代,神理解递归。”


引言:当你还沉浸在C语言指针的余韵中时,招聘网站上的岗位要求已经写满了“熟练掌握数据结构与算法”。你是否好奇,为什么面试官总爱问“这段代码的时间复杂度是多少”?因为不懂复杂度分析,写出的程序可能就是一场效率灾难。本篇将为你揭秘如何评判代码的效率,让你从“能跑就行”进阶到“跑得飞快”。

目录

1.  基本介绍

1.1 数据结构

1.2  算法

1.3  重要性

2.  思路选择的标准——>复杂度

2.1  介绍复杂度(重要,理解)

3.  时间复杂度

3.1  大O的渐进表示法

3.2  时间复杂度计算(常见的时间复杂度推理,掌握方法)

大O的渐进表示法选择:

对数书写注意:

4.  空间复杂度

4.1  空间复杂度计算

5.  常见复杂度对比

6.  复杂度算法题


1.  基本介绍

1.1 数据结构

--是计算机存储、组织(增删查改)数据的方式,指相互之间一种或多种特定关系的数据元素的集合。没有一种单一的数据结构对所有用途都有用,所以要学习各种的数据结构:线性表、树、图、哈希...(包括数组)

1.2  算法

--简单说,算法是满足严格条件的详细的解题思路(计算步骤),将数据输入转换为结果输出。

--当然一道算法题有多种解题思路,选择哪个——>看复杂度!——>重点注意!!

1.3  重要性

--数据算法不分家,二者的考量在校园招聘频频出现,且大多位编程题(考验写代码的能力)。

--学好数据结构的方法:

  • 死磕代码,代码勤练,多刷题,前期没思路?无妨,从模仿开始!
  • 画图+思考,有些题经过画图动态理解运行过程,可能就有思路了。

2.  思路选择的标准——>复杂度

--先由例题引出问题:力扣_189. 轮转数组

 --这是轮转一回的。

--根据这样的思路:每次都将最后一位放在临时变量中,前面的元素依次向后移动一位;如此共三次(k次)。

void rotate(int* nums, int numsSize, int k) { while(k--) { //将最后一位先放在一边 int temp = nums[numsSize - 1]; //循环进行轮换 for(int i = numsSize - 1; i > 0; i--) { nums[i] = nums[i - 1]; } //将最后一个放在最前面 nums[0] = temp; } }

--但发现最后提示超出时间限制。代码本身思路没问题,那只能是运行时间太长了,效率低。涉及复杂度,来判断代码的效率。

2.1  介绍复杂度(重要,理解)

--算法编写完程序后,运行会占用时间、空间(内存),这两个元素就是衡量算法好坏的标准:时间复杂度、空间复杂度。(主要是空间维度,越小越好)

--在校招也会对复杂度进行考察!


3.  时间复杂度

--定义:时间复杂度是个函数式:T(N),定量描述算法运行时间。(N指代所有变量)

        --时间复杂度:衡量程序的时间效率(变化趋势的快慢)。

--为什么不计算运行时间?不同机器配置、编译环境,导致时间不同;时间只能在程序运行后得到,无法由理论思想评估。

--T(N),计算了程序执行次数。C语言编译链接部分,知道算法程序编译生成二进制指令,cpu执行这些指令。

--由代码或理论思想计算T(N),假定指令执行时间一样,那么执行次数N就占大头——>近似的,次数代表时间效率的好坏,T(N)越大,效率一定是低的。

程序运行时间 = 二进制指令运行时间 * 执行次数

(假定时间是一定的——>常量;次数是最大影响因素)

(主要看循环语句的执行次数;非循环语句只执行一次,不计算,总体影响不大。)

--例1

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

--为什么直接为N^2?

由表格知:变量N是未知的,N变化时,对T(N)影响最大的是N^2,相比其他M是已知的的忽略不计。

--计算时间复杂度,只是比较增长量级。当变量N变化时,观察T(N)的变化趋势。上面看到低阶项、常数项影响很小,只用计算大概执行次数来表示变化趋势,通常使用大O的渐进表示法。

3.1  大O的渐进表示法

--大O符号:描述函数渐进行为的符号。规则:

  • T(N),只保留最高阶项,去掉低阶项;
  • 若最高项存在且不为1,可以去掉项的系数;
  • T(N)中若没有与N有关的项,只有常数项,用1取代所有加法常数;

--上面Func1时间复杂度:O(N^2).

3.2  时间复杂度计算(常见的时间复杂度推理,掌握方法)

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

--练习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); }
--前面说过,N指代所有变量,直接是2N。

--练习3:

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

--练习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; }
大O的渐进表示法选择:
💡总结:发现有些时间复杂度存在最好、最坏、平均的情况

最坏:任意输入规模的最大运行次数——上界;

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

最好:任意输入规模的最小运行次数——下界;

--大O的渐进表示法关注上界

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

--练习6:

 void func5(int n) { int cnt = 1; while (cnt < n) { cnt *= 2; } }
对数书写注意:
注意:对于对数的书写,当n接近无穷大时,底数大小对结果影响不大,可以省略。直接表示为log n。

--练习7:

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

4.  空间复杂度

--定义:是数学表达式,描述算法在运行中除原始的输入数据所占空间外,为了运行算法而额外临时申请的空间大小

  • 不算输入数据本身:输入数据所占空间是必须的,不属于“额外开销”。
  • 算大小未知的空间:算法运行过程中,函数体内临时定义的变量、数组、递归栈帧等所占空间。(一般情况可以说算的是变量的个数。

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

--练习2:这就要看函数栈帧空间的大小了

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

--补充:数组动态开辟内存,大小未知,O(N)。

void func(int n) { int* arr = (int*)malloc(n * sizeof(int)); } 

5.  常见复杂度对比


6.  复杂度算法题

 力扣_189. 轮转数组

--思路一

时间复杂度O(N^2),空间复杂度O(N^2)

--复杂度高,效率低,舍弃

void rotate(int* nums, int numsSize, int k) { while(k--) { //将最后一位先放在一边 int temp = nums[numsSize - 1]; //循环进行轮换 for(int i = numsSize - 1; i > 0; i--) { nums[i] = nums[i - 1]; } //将最后一个放在最前面 nums[0] = temp; } }

--思路二

时间复杂度O(N),空间复杂度O(N);

--比上一思路时间减少了,但空间增大。这就是常见的空间换时间。

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

--思路三

时间复杂度:O(N),空间复杂度:O(1)

void reverse(int* nums, int left, int right) { while (left < right) { //交换 int temp = nums[left]; nums[left] = nums[right]; nums[right] = temp; left++; right--; } } void rotate(int* nums, int numsSize, int k) { k = k % numsSize;//防止出现k>numsSize出错 //前n-k个数据逆置 reverse(nums, 0, numsSize - 1 - k); //后k个数据逆置 reverse(nums, numsSize - k, numsSize - 1); //整体逆置 reverse(nums, 0, numsSize - 1); }

结语:复杂度分析就像我们踏入数据结构与算法世界获得的第一件“标尺”,它或许有些抽象,但却是我们未来选择最优解决方案最可靠的依据。不必为一时的不熟练而焦虑,多看、多练、多画图,这种感觉自然会变得敏锐。——本篇是「数据结构与算法」系列的开篇之作,希望能为你打下坚实的基础。接下来的旅程,我将与你一同深入更多精彩的数据结构。你的每一个点赞、评论和关注,都是我持续创作的最大动力!我们下期再见!

Read more

【花雕学编程】Arduino BLDC 之自主巡逻机器人(避障+路径规划)

【花雕学编程】Arduino BLDC 之自主巡逻机器人(避障+路径规划)

基于 Arduino 的无刷直流电机(BLDC)自主巡逻机器人(避障+路径规划),是一个融合了高效动力系统、多传感器环境感知、嵌入式实时计算与智能决策算法的复杂移动机器人系统。它旨在替代人工在预设或未知环境中进行长时间、高效率的巡查任务,通过 BLDC 电机提供持久且敏捷的驱动力,并利用算法实现环境理解与自主导航。 1、主要特点 高效长续航 BLDC 驱动系统 BLDC 电机是巡逻机器人的“心脏”,决定了其机动性与作业时长。 高效率与长续航: 相较于有刷电机,BLDC 电机效率通常高于 85%,发热量低。配合电子调速器(ESC)的 FOC(磁场定向控制)算法,能最大限度地利用电池能量,确保机器人能够持续工作 8 小时甚至更长时间,满足长时间巡逻的需求。 高动态响应: 巡逻过程中常需急停、避让行人或车辆。BLDC 电机具备快速启停和快速加减速的能力,配合差速转向底盘,能迅速响应避障算法发出的紧急制动或转向指令,保证运行安全。

By Ne0inhk
小米 “养龙虾”:手机 Agent 落地,智能家居十年困局被撬开

小米 “养龙虾”:手机 Agent 落地,智能家居十年困局被撬开

3月6日,小米正式推出国内首个手机端类 OpenClaw Agent 应用 ——Xiaomi miclaw,开启小范围邀请封测。这款被行业与网友戏称为小米 “开养龙虾” 的新品,绝非大模型浪潮下又一款语音助手的常规升级,而是基于自研 MiMo 大模型、具备系统级权限、全场景上下文理解能力的端侧智能体。 作为深耕智能家居领域的行业媒体,《智哪儿》始终认为:智能家居行业过去十年的迭代,始终没能跳出 “被动执行” 的底层困局。而 miclaw 的落地,不止是小米在端侧 AI 赛道的关键落子,更是为整个智能家居行业的底层逻辑重构,提供了可落地的参考范本。需要清醒认知的是,目前该产品仍处于小范围封测阶段,复杂场景执行成功率、端侧功耗表现、第三方生态适配进度等核心体验,仍有待大规模用户实测验证。本文将结合具象场景、量化数据与多维度视角,客观拆解 miclaw 的突破价值、现实挑战,以及它对智能家居行业的长期影响。 01 复盘行业困局:智能家居十年 始终困在 “被动执行”

By Ne0inhk
【花雕学编程】Arduino BLDC 之离线语音模块智能控制机器人

【花雕学编程】Arduino BLDC 之离线语音模块智能控制机器人

基于 Arduino 的无刷直流电机(BLDC)离线语音模块智能控制机器人,是一种将嵌入式语音识别技术与高效电机控制深度融合的独立式智能系统。该机器人通过本地化的语音处理单元,实现对 BLDC 执行机构的直接指令控制,摆脱了对云端服务器或外部网络的依赖。这种架构不仅保障了控制的实时性与隐私安全,也极大地拓展了人机交互的便捷性。 1、主要特点 本地化语音处理与隐私安全 这是该系统的核心优势,所有的语音信号处理与指令识别均在本地硬件上完成。 数据隐私保护: 语音数据无需上传至互联网,完全在本地闭环处理,从根本上杜绝了用户语音隐私泄露的风险,符合高安全等级应用的需求。 超低延迟响应: 省去了网络传输、云端服务器排队和数据回传的时间,指令识别的响应速度极快(通常在 100ms 级别)。这种即时性对于控制高速运转的 BLDC 电机至关重要,确保了操作的流畅性和安全性。 离线独立运行: 系统不依赖 Wi-Fi 或蓝牙等通信链路,即使在网络信号差或无网络的环境下(如地下室、封闭车间),依然能稳定工作,系统鲁棒性极强。 高保真语音识别与指令集管理 离线语音模块通常采用专用的 DSP 或低功耗 AI

By Ne0inhk
【FPGA入坑指南第二章】安装vivado/vitis2023.1软件

【FPGA入坑指南第二章】安装vivado/vitis2023.1软件

本栏目的初心 降低FPGA的门槛,让所有对FPGA感兴趣的,之前望而却步的朋友也能上手玩一玩,体验一下FPGA的世界。【本栏作者贯彻“先进入再深入”的中心思想】 引文 * AMD官方软件下载地址 vivado开发者工具 * 百度云下载包 Xilinx2023.1安装包「其他版本可以联系作者」 简介 Vivado和Vitis是Xilinx(现为AMD的一部分)推出的两款核心软件工具,它们在FPGA和SoC(系统级芯片)设计中占据着重要地位。这两款软件的推出代表了Xilinx在数字设计领域的持续创新与发展,并且逐步取代了早期的ISE和SDK工具套件。 ISE和SDK的历史背景 在Vivado和Vitis推出之前,Xilinx的ISE(Integrated Software Environment)是FPGA设计的主要开发环境。ISE主要用于Xilinx早期的FPGA系列,如Spartan和Virtex系列。ISE支持从RTL设计、综合、布局布线到生成比特流文件的整个设计流程,但其在时序优化、设计复杂度和开发效率方面逐渐暴露出一些局限性,尤其是对于更高端的FPGA系列和

By Ne0inhk