算法竞赛进阶指南学习--0x20

文章介绍了图和树的数据结构存储方式,如邻接矩阵和邻接表,并详细阐述了深度优先搜索(DFS)和广度优先搜索(BFS)在树和图中的应用,包括树的DFS序、深度、重心的计算,以及图的连通块划分和拓扑排序等概念与算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 注意:我们统一用邻接矩阵和邻接表来存储树和图

int h[N], e[N], ne[N], w[N], idx;
int g[N][N];
void add(int a, int b, int c) //a->b 边权为c
{
    e[idx] = b;
    w[idx] = c
    ne[idx] = h[a];
    h[a] = idx ++;
}

void add(int a,int b, int c)
{
    g[a][b] = c;
}

0x21树和图的遍历

树与图的dfs遍历,dfs序,深度和重心

dfs遍历树与图

void dfs(int x)
{
    vis[x] = true;
    for(int i = h[x]; i != -1 ; i = ne[i])
    {
        int y = e[i];
        if(vis[y]) continue;
        dfs(y);
    }
}

时间复杂度为边数加节点数M+N

时间戳

 即每个节点被访问的顺序,我们只需要记录每一个节点被打上“已访问”时的cnt即可代码如下

int cnt[N], t = 1;
void dfs(int x)
{
    cnt[x] = t ++;
    for(int i = h[x]; i != -1 ; i = ne[i])
    {
        int y = e[i];
        if(cnt[y]) continue;
        dfs(y);
    }
}

树的DFS序

我们在对树进行dfs时,对于每个节点,刚进入递归和即将回溯前记录一次该点的编号,最终得到的长度为2N的节点序列就称为DFS序,记每个节点编号为x在序列中出现的位置分别为L[x]和R[x]那么在[L[x],R[x]]内就是以x为根的子数的DFS序,后续问题会涉及此技巧,代码如下:

int a[N], k;
void dfs(int x)
{
    a[k++] = x;  //记录第一次访问x节点
    vis[x] = true;
    for(int i = h[x]; i != -1 ; i = ne[i])
    {
        int y = e[i];
        if(vis[y]) continue;
        dfs(y);
    }
    a[k++] = x; //记录x节点回溯
}

二叉树的先序

二叉树的中序

二叉树的后序

树的深度

树的深度是一种自顶向下的属性,我们可以在遍历的过程中结合递推记录下该属性,代码如下:

int d[N];
void dfs(int x)
{
    vis[x] = true;
    for(int i = h[x]; i != -1 ; i = ne[i])
    {
        int y = e[i];
        if(vis[y]) continue;
        d[y] = d[x] + 1;//y是x的子节点,则深度等于父节点深度+1
        dfs(y);
    }
}

树的重心

在介绍树的重心之前,我们先来介绍一下以每个节点x为根的子树大小size[x],这是一种自底向上的属性,求得每个节点对应的size代码如下:

int sz[N];

void dfs(int x)
{
    vis[x] = true;
    sz[x] = 1; //子树x的大小,包含其本身
    for(int i = h[x]; i != -1 ; i = ne[i])
    {
        int y = e[i];
        if(vis[y]) continue;
        dfs(y);
        sz[x] += sz[y];
    }
}

 树的重心是一个节点,对于节点x,把该节点删去后,原来的一颗树会划分成若干子树,记这些子树的最大sz等于max_part(x),那么重心p就是使得max_part(x)取得最小值的x,通过下面的代码我们可以得出p节点以及max_part(p):

int sz[N], ans = 0x3f3f3f3f, pos;

void dfs(int x)
{
    int max_part = 0;
    vis[x] = true;
    sz[x] = 1; //子树x的大小,包含其本身
    for(int i = h[x]; i != -1 ; i = ne[i])
    {
        int y = e[i];
        if(vis[y]) continue;
        dfs(y);
        sz[x] += sz[y];
        max_part = max(max_part, sz[y]);
    }
    max_part = max(max_part, n - sz[x]);
    if(max_part < ans)
    {
        pos = x;
        ans = max_part;
    }
}

图的连通块划分

连通块,就是在一个子图中,任意两个点之间都存在一条路径,dfs每从x开始遍历,就会访问x能够到达的所有点与边,因此对N个节点做一次dfs就能划分出一张无向图中的各个连通块,或者是森林中的每课树,代码如下:

int vis[N], cnt;
//cnt记录了连通块的个数,同时也划分了每个点属于哪个连通块,vis[x]标记x属于第几个连通块

void dfs(int x)
{
    vis[x] = cnt;
    for(int i = h[x]; i != -1 ; i = ne[i])
    {
        int y = e[i];
        if(vis[y]) continue;
        dfs(y);
    }
}

    for(int i = 1 ; i <= n ; i ++)
    {
        if(vis[i]) continue;
        cnt ++;
        dfs(i);
    }

树与图的BFS,拓扑排序

树与图的BFS

我们用一个队列queue来实现,每次从队头取出一个节点x,沿着x节点往后走,把尚未访问过的节点加入队尾,代码如下:

int d[N];//用d[x]来表示节点在树中的深度,或者表示在图中x到起点的最少步数(+1)

void bfs()
{
    memset(d, 0, sizeof d);
    queue<int> q; q.push(1); d[1] = 1;
    while(q.size())
    {
        int ver = q.front();
        q.pop();
        
        for(int i = head[ver], i != -1; i = ne[i])
        {
            int y = e[i];
            if(d[y]) continue;
            q.push(y);
            d[y] = d[x] + 1;
        }
    }
}

拓扑排序

给定一张有向无环图,存在这样一个由图中所有点构成的序列,对于图中的所有边x->y,x在A中都出现在y之前,则称A是该有向无环图的一个拓扑序列,可以证明有向无环图才至少存在一个拓扑序列,其求解方案如下:

  • 预处理出所有点的入度deg[i],把所有入度为0的点加入队列中
  • 取出队头节点x,把x加入拓扑序列seq的末尾
  • 遍历x出发的所有边(x,y), 把deg[y] 减1,若被减为0,则把y入队
  • 重复前面的过程,直到队列为空

我们每一次加入入度为0的点,就能保证在这个节点加入之前,没有任何节点有边指向该节点,如此便能保证拓扑序列的要求。

最后按顺序输出seq序列中的数字即可

// seq存储拓扑序列, deg记录每个点的入度
int seq[N], deg[N], cnt;

bool topsort()
{
    queue<int> q;
    for(int i = 1; i <= n ; i ++)
        if(!deg[i]) q.push(i);
        
    while(q.size())
    {
        int t = q.front();
        q.pop();
        seq[++cnt] = t;
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int y = e[i];
            if(--deg[y] == 0) q.push(y);
        }
    }
    
    return cnt == n;
}

拓扑排序可以用来判断有向图中是否存在环,若seq的序列长度小于图中点的数量,就说明某些节点未被遍历,进而说明图中有环的存在,进一步的解释如下:

先来看一下求拓扑序列的算法, 我们每次--deg[i]实际就是擦除i这个节点的一条边的过程,而对于无环图来说,我们一定能擦除掉任何一个节点的所有入边。(如下图)

 这是因为我们是自顶向下的擦除,取的是队列中的节点,并擦除该节点指向的节点的这条边,若存在环的话(如下图)

 对于B这个点,入度为2,我们最多只能通过A节点擦除一条A->B的边,使其入度为1,之后就没有途径再使其入度减小,那么节点B就不会加入到队列中,继而就不会加入到拓扑序列中。

<think>好的,我现在需要帮用户解答“学习单片机的路径”这个问题。首先,用户之前已经问过如何理解单片机,现在想进一步了解学习路径。我需要确保回答结构清晰,分步骤进行,并且内容真实可靠。 首先,我应该回顾之前的回答,看看有没有可以延续的内容。之前的回答分为五个方面:定义、硬件结构、工作原理、应用场景和与计算机的对比。用户现在需要的是学习路径,可能需要从基础知识到实践再到深入学习的步骤。 接下来,我需要确定学习单片机的典型路径。通常学习单片机需要从基础电子知识开始,然后学习具体单片机类型,比如51单片机,再进入编程和开发工具的使用,接着进行项目实践,最后深入学习高级主题。同时,可能需要推荐一些资源,比如书籍、开发板等。 用户可能的背景是初学者,对单片机感兴趣但缺乏系统指导。他们可能希望有一个明确的步骤,从易到难,逐步掌握。因此,我需要将路径分解成几个阶段,每个阶段包含具体的学习内容和目标。 需要注意用户可能关心的问题,比如需要哪些预备知识、推荐哪些开发板、如何选择编程语言、以及如何通过项目巩固知识。同时,要避免使用过于专业的术语,保持解释的易懂性。 还要考虑用户可能没有提到的需求,比如是否需要学习电路设计、如何调试程序、以及如何接触实际应用案例。这些内容虽然用户没有直接提到,但作为学习路径的一部分,可能对用户有帮助。 最后,要确保回答符合之前的系统级指令,使用正确的中文,数学公式用$...$格式,独立公式用$$...$$,并且结构清晰。可能需要分点列出每个阶段,并给出具体例子和推荐资源,帮助用户逐步学习。</think>以下是学习单片机的系统化路径,分为六个阶段,结合理论与实践,逐步构建完整的知识体系: --- ### 一、基础准备阶段(1-2周) **核心目标**:建立硬件与编程基础认知 1. **电子基础**: - 掌握欧姆定律$V=IR$、三极管开关原理 - 理解数字电路(逻辑门、ADC/DCC转换) - 熟练使用万用表测量电压/电流 2. **编程基础**: - 学习C语言指针操作(如`int *p = &a;`) - 掌握位运算(`PORT |= (1 << 3);`) - 理解二进制/十六进制转换($0x3F \rightarrow 00111111_2$) --- ### 二、入门实践阶段(3-4周) **推荐平台**:STC89C52(51单片机)或STM32F103C8T6 1. **开发环境搭建**: - 安装Keil μVision或PlatformIO - 烧录工具配置(STC-ISP/ST-Link) 2. **基础实验**: - LED流水灯(GPIO控制) ```c while(1) { P1 = 0xFE; // 11111110 Delay_ms(500); P1 = 0xFD; // 11111101 } ``` - 数码管显示(动态扫描原理) - 按键检测(消抖算法$t_{debounce} \geq 10\text{ms}$) --- ### 三、外设开发阶段(4-6周) **核心模块**:通过数据手册理解寄存器配置 | 模块类型 | 关键技术点 | 典型应用公式 | |---------------|------------------------------|---------------------------| | 定时器 | 中断服务函数配置 | $T_{溢出} = \frac{12}{f_{osc}} \times (2^{16}-TH0)$ | | ADC转换 | 分辨率计算($N=\log_2(\frac{V_{ref}}{LSB})$) | 电压计算$V_x = \frac{D_{out} \times V_{ref}}{2^n}$ | | PWM输出 | 占空比调节($D=\frac{CCR}{ARR}$) | 电机转速控制$v = k \cdot D$ | | 串口通信 | 波特率计算($TH1 = 256 - \frac{f_{osc}}{12 \times 32 \times Baud}$) | ASCII协议解析 | --- ### 四、系统设计阶段(2-3个月) **项目实战**(推荐顺序): 1. **智能温控系统**: - 使用DS18B20传感器(温度转换公式$T = \frac{raw}{16}$) - PID算法实现($u(t) = K_p e(t) + K_i \int e(t)dt + K_d \frac{de(t)}{dt}$) 2. **物联网终端**: - ESP8266 WiFi模块AT指令控制 - MQTT协议传输数据(报文格式`{"temp":25.6,"hum":60}`) 3. **电机控制系统**: - L298N驱动H桥电路 - 闭环控制(编码器脉冲计数$n = \frac{\theta}{360} \times PPR$) --- ### 五、进阶提升阶段(6个月+) **技术拓展方向**: 1. **RTOS应用**: - FreeRTOS任务调度(优先级抢占机制) - 信号量/消息队列实现多任务同步 2. **低功耗设计**: - 睡眠模式切换(功耗对比:运行模式$5\text{mA}$ vs. 休眠模式$2\mu\text{A}$) - 电源管理策略(看门狗定时唤醒) 3. **硬件优化**: - PCB设计(Altium Designer布局规则) - EMI抑制(滤波电容选择$C = \frac{I \cdot \Delta t}{\Delta V}$) --- ### 六、学习资源推荐 1. **经典教材**: - 《手把手教你学51单片机》(C语言版) - 《STM32库开发实战指南》 2. **开发工具包**: - 普中科技51开发板(带1602液晶模块) - STM32F4 Discovery套件(含加速度计/音频编解码) 3. **在线平台**: - 立创EDA(硬件设计) - GitHub开源项目(搜索"STM32 Project") --- **关键学习技巧**: - 每天坚持调试至少1个外设模块 - 使用示波器观察信号波形(如PWM上升时间$t_r < 10\text{ns}$) - 建立调试日志(记录异常现象与解决方案) - 参与电子设计竞赛(如全国大学生电子设计竞赛) 通过上述路径的系统学习,可逐步从单片机操作员成长为能独立完成嵌入式系统设计的工程师。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

RefrainC.C.

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值