- 2025-02-21
-
发表了主题帖:
《Hello算法》走迷宫——深度优先、广度优先搜索
《Hello算法》有在线版学习的页面,看起来挺方便的。
曾经在视频中看见过迷宫小车走迷宫的,一直幻想着自己能有一辆小车,去写跑迷宫的算法,可惜至今任然未能实现。
走迷宫算法可以转换成图的数据结构去处理:
在这个图中寻找到一条能贯通指定两个节点的路径,就是迷宫的解。
遍历图寻找解有广度优先搜索(BFS)、深度优先搜索(DFS)。
广度优先搜索是一种逐层扩展的搜索算法。它从起点开始,逐层向外扩展,直到找到终点。BFS能够找到从起点到终点的最短路径。如同给迷宫灌水,水无差别地流淌,逐层灌下去。
实现步骤
初始化队列,将起点加入队列并标记为已访问。
从队列中取出一个节点,探索其所有未访问的节点相邻,并将这些节点加入队列。
重复上述步骤,直到队列为空或找到终点。
优点和缺点
优点:
能够找到最短路径。
实现相对简单。
缺点:
空间复杂度较高,因为需要存储大量节点。
深度优先搜索是一种用于遍历或搜索树或图的算法。它从起点开始,沿着一条路径尽可能深地探索,直到无法继续为止,然后回溯到上一个未完全探索的节点,继续搜索未访问的分支。类似于“永远沿着左边(右边)走,总能找到出路”的迷宫解法。
实现步骤
从起点开始,标记起点为已访问。
沿着一个方向(如上、下、左、右)递归地探索下一个节点。
如果遇到障碍物或已访问的节点,则回溯到上一个节点。
如果到达终点,则找到一条路径。
如果所有路径都探索完毕仍未找到终点,则无解。
优点和缺点
优点:
实现简单,代码易于编写。
空间复杂度相对较低。
可快速找到一条可行路径。
缺点:
找到的路径不一定是最短路径。
如果迷宫中有大量死胡同DFS,可能会陷入较深的路径,导致时间复杂度较高。
如果我有一辆迷宫小车,我会采用深度优先的算法,这样记忆的路径会偏少,能够尽快地走出迷宫。
- 2025-02-17
-
回复了主题帖:
机器人开发话题征集:写啥你来定!
觉着分两块吧!一个与人交互提供反馈,如各类聊天机器人,DeepSeek模型。这块发展的飞快。
一个是具体的执行机构,如各种关节机构,运动机构。这块发展感觉还是很滞后。
总体感觉很快就会有飞跃式的发展了!
- 2025-02-15
-
回复了主题帖:
《Hello算法》哈希表的认识
Jacktang 发表于 2025-2-15 10:15
看起来这个MD5的哈希算法还是好好学学哈
哈哈!算法挺复杂的,从来没能看明白过。直接用现成的就好!
- 2025-02-14
-
发表了主题帖:
《Hello算法》哈希表的认识
本帖最后由 aramy 于 2025-2-14 15:50 编辑
简单认识哈希表:
哈希表(Hash Table),是一种非常高效的数据结构,它通过哈希函数将键(key)映射到表中的一个位置来访问记录,从而实现快速的数据插入、查找和删除操作。
书中介绍了哈希表的一些算法,作为一种算法的介绍是比较简单的,哈希表在日常工作中接触的最多的大概就是MD5算法了吧。
回顾一下哈希表算法的目标:
日常中常见的MD5算法,就是一个哈希算法的实现。
单向性:MD5算法,将结果映射到16字节的散列。使得结果散列与待测字符串之间毫无关联,现在网上所谓的MD5破解,也只能是通过查表方式进行对碰,简单的字符串还能实现,复杂的就很困难。
抗碰撞性:这个找到一条旧闻,在2004年山东大学王小云教授成功破解MD5,能够找到不同的字符串,对应相同的MD5码!但是直到今天,MD5码依然广泛使用,证明了期抗碰撞性的顽强。
雪崩效应:记得以前学习古典密码时,遇到的移位算法的破解方法就是通过统计学计算不同字母的出现频率。然后制作移位字典,反向破解。MD5的哈希算法,在这里就实现的很优秀,只要源字符串有一丁点变化,结果就千差万别,而且无法统计出特定的规律。
- 2025-02-08
-
回复了主题帖:
这个春节最火爆的AI大模型deepseek,你玩了吗?
观望中,貌似钓鱼的APP已经出来了。
- 2025-02-04
-
发表了主题帖:
《Hello算法》增删改查——数组与链表
对数据的基础处理方法有增、删、改、查。四种基本操作。链表和数组都能支持这些基本操作。
1、查与改
数组是一种线性数据结构,数组元素被存储在连续的内存空间中,那么对于数组而言,可以直接定位到数组中的任意一个元素,所以复杂度为O(1)。定位到具体元素后即可做修改操作。
链表也是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。与数组不同,链表无法直接定位到任意一个元素,只能从头节点出发,逐个节点地遍历,直至找到目标节点。复杂度为O(n)。同样定位到具体元素,即可做修改操作。
2、增与删
数组应为元素是被存储在连续的内存空间里,并且内存空间在初始化时就被指定了。所以增加和删除元素变得很麻烦。增加元素时,需要重新申请一整块内存空间,新内存空间尺寸为当前内存空间加一;然后将当前内存空间中的元素与增加的元素,逐一地搬运到新的内存空间中去(这里存在申请新内存失败的风险)。删除元素时可以不用新申请内存空间,但是指定位置元素删除后,其后方所有的元素,都需要向前移动位置,形成新的数组。数组的增与删,内存操作都非常频繁,复杂度为O(n)。
链表在增与删上则方便很多,新增元素时:第一步将新增元素的下一节点指针指向需要插入位置的下一节点地址;第二步将需要插入位置的节点的下一元素指针,指向新增元素即可。
删除元素:第一步将指向待删除元素的指针,修该为待删除元素指向的下一节点地址;第二步释放待删除元素。链表的增和删,复杂度O(1)。
最后 关于数组与链表的总结,书上总结的非常到位:
数组和链表是两种基本的数据结构,分别代表数据在计算机内存中的两种存储方式:连续空间存储和分散空间存储。两者的特点呈现出互补的特性。
数组支持随机访问、占用内存较少;但插入和删除元素效率低,且初始化后长度不可变。
链表通过更改引用(指针)实现高效的节点插入与删除,且可以灵活调整长度;但节点访问效率低、占用内存较多。常见的链表类型包括单向链表、环形链表、双向链表。
列表是一种支持增删查改的元素有序集合,通常基于动态数组实现。它保留了数组的优势,同时可以灵活调整长度。
列表的出现大幅提高了数组的实用性,但可能导致部分内存空间浪费。
程序运行时,数据主要存储在内存中。数组可提供更高的内存空间效率,而链表则在内存使用上更加灵活。
缓存通过缓存行、预取机制以及空间局部性和时间局部性等数据加载机制,为 CPU 提供快速数据访问,显著提升程序的执行效率。
由于数组具有更高的缓存命中率,因此它通常比链表更高效。在选择数据结构时,应根据具体需求和场景做出恰当选择。
- 2025-01-31
-
回复了主题帖:
【Luckfox Pico Max评测】TMF8821驱动移植与测试
大佬!牛逼!
- 2025-01-22
-
回复了主题帖:
《Hello算法》开卷有益——迭代和递归算法
eew_Ya3s2d 发表于 2025-1-22 14:49
表达是挺简便的,但是计算的时候是不是会反复调用函数,是不是调用的数据越大,迭代的次数越大,消耗的时间 ...
迭代不会。迭代不会反复调用自身,如果有子函数,每次调用结束后就释放了资源。但是递归会,递归会重复调用自身,每次调用,都要将当前状态入栈,所以空间消耗比较大。
-
发表了主题帖:
《Hello算法》开卷有益——迭代和递归算法
计算机中算法是个奇怪的东西。首先是极其重要,生活中各种问题的解决都需要使用到算法,计算机软件就是一个由算法构成的庞大工程,但另一方面,它又暗暗隐藏,平时能看见的各种软件都已经将算法隐藏在其中了,可以无需了解算法的实现,而且使用各种软件。不了解算法也可以编程,但是想编写出优秀的程序,必须了解算法。
关于迭代和递归,书中给了个例子:给定一个斐波那契数列 0,1,1,2,3,5,8,13,… ,求该数列的第N个数字。
使用递归的例子:对于递归来说,终止条件至关重要,如果没有终止条件,则会陷入“鸡生蛋,蛋生鸡”的无限循环之中……
def fib(n: int) -> int:
"""斐波那契数列:递归"""
# 终止条件 f(1) = 0, f(2) = 1
if n == 1 or n == 2:
return n - 1
# 递归调用 f(n) = f(n-1) + f(n-2)
res = fib(n - 1) + fib(n - 2)
# 返回结果 f(n)
return res
使用迭代则比较好理解,从初始条件出发,一直计算到满足终止条件即可。日常中解决问题的思路以迭代居多。
书中给出了迭代和递归的比较图。
迭代与递归特点对比
迭代
递归
实现方式
循环结构
函数调用自身
时间效率
效率通常较高,无函数调用开销
每次函数调用都会产生开销
内存使用
通常使用固定大小的内存空间
累积函数调用可能使用大量的栈帧空间
适用问题
适用于简单循环任务,代码直观、可读性好
适用于子问题分解,如树、图、分治、回溯等,代码结构简洁、清晰
通过对比可以看出,递归在时间效率和内存使用是都劣于迭代,但是迭代处理的问题,通常会在处理前对问题有清晰的路径和方法,算法中使用循环去解决问题。而递归则通常对问题只有收敛的条件,具体解决过程负责或冗长,使用递归不停地对函数自身进行调用,直至解决完问题,对于复杂问题有着优势。
- 2025-01-15
-
回复了主题帖:
这个电源中78L05的上面电阻是什么作用?
有没有一种可能,右边是一个简单的电阻负载,比如一个LED灯,串上电阻就能正常工作。7805只是设计时用来忽悠的,等生产了只保留个电阻了事。
- 2025-01-14
-
回复了主题帖:
读书活动入围名单:《hello算法》
个人信息无误,确认可以完成阅读分享计划!
- 2024-12-18
-
回复了主题帖:
泰坦触觉 TITAN Core开发套件的示例工程代码给大家要来啦
想玩,可惜机会好少,感觉渺茫!
- 2024-12-08
-
回复了主题帖:
【Follow me第二季第3期】EK-RA6M5任务提交
御坂10032号 发表于 2024-12-8 18:30
这个树莓派有固件吗佬
应该是stm芯片的。
-
回复了主题帖:
【Follow me第二季第3期】EK-RA6M5任务提交
御坂10032号 发表于 2024-12-8 18:30
这个树莓派有固件吗佬
没有,按示波器购买的,到手就是这样,不过据说开源,没去找过源码。这个小东西叫 梅雀林
- 2024-11-28
-
加入了学习《FollowMe 第二季:3 - EK_RA6M5 开发板入门》,观看 EK-RA6M5 开发板入门
-
发表了主题帖:
【Follow me第二季第3期】EK-RA6M5任务提交
本帖最后由 aramy 于 2024-12-3 20:32 编辑
很开心参加“Follow me第二季第3期”活动,这次板子是瑞萨的EK-RA6M5 。这款开发板超级大的。还配了网线、usb转接线和microUSB数据线。
入门任务:搭建环境,下载调试示例程序,Blink,按键
开发软件,我是使用官方的IDE,内核应该是eclipse。参考着老师的视频安装IDE环境,我这里安装的是“setup_fsp_v5_5_0_e2s_v2024-07.exe”。然后下载例程包“ra-fsp-examples-5.6.0.example.1.zip”,导入例程包中的“_quickstart”例程。
点击图标栏里边的“锤子”图标,或者在项目上右键鼠标选择构建,即可编译项目,有一些警告,不用管它。然后通过microUSB线,插到板子的“usb full”口。官方例程中提供了按键和LED的控制。蓝色LED闪烁。左边的按钮控制蓝色LED灯的亮度:10%、50%、90%;右边的按键控制闪烁频率1Hz、5Hz、10Hz。查看例程的源码,可以找到控制LED灯亮度的方法:
void gpt_blue_callback(timer_callback_args_t * p_args)
{
/* Void the unused params */
FSP_PARAMETER_NOT_USED(p_args);
switch (s_blueled_flashing)
{
case ON:
{
if ((s_intense++ ) < s_duty)
{
TURN_BLUE_ON
}
else
{
TURN_BLUE_OFF
}
if (s_intense >= 100)
{
s_intense = 0;
s_duty = g_pwm_dcs[g_board_status.led_intensity];
}
break;
}
default:
{
TURN_BLUE_OFF
s_intense = 0;
s_duty = g_pwm_dcs[g_board_status.led_intensity];
}
}
}
两个按键使用了中断的处理方法。
/* SW 1 */
/**********************************************************************************************************************
* Function Name: button_irq10_callback
* Description : SW1 Interrupt handler.
* Argument : p_args
* Return Value : None
*********************************************************************************************************************/
void button_irq10_callback(external_irq_callback_args_t *p_args)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
BaseType_t xResult = pdFAIL;
EventBits_t uxBits;
/* Void the unused args */
FSP_PARAMETER_NOT_USED(p_args);
uxBits = xEventGroupGetBitsFromISR (g_update_console_event);
if ((uxBits & (STATUS_UPDATE_INTENSE_INFO)) != (STATUS_UPDATE_INTENSE_INFO))
{
/* Cast, as compiler will assume calc is int */
g_board_status.led_intensity = (uint16_t) ((g_board_status.led_intensity + 1) % 3);
xResult = xEventGroupSetBitsFromISR(g_update_console_event, STATUS_UPDATE_INTENSE_INFO,
&xHigherPriorityTaskWoken);
/* Was the message posted successfully? */
if (pdFAIL != xResult)
{
/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context
switch should be requested. The macro used is port specific and will
be either portYIELD_FROM_ISR() or portEND_SWITCHING_ISR() - refer to
the documentation page for the port being used. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
/**********************************************************************************************************************
End of function button_irq10_callback
*********************************************************************************************************************/
/* SW 2 */
/**********************************************************************************************************************
* Function Name: button_irq9_callback
* Description : SW2 interrupt handler.
* Argument : p_args
* Return Value : None
*********************************************************************************************************************/
void button_irq9_callback(external_irq_callback_args_t *p_args)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
BaseType_t xResult = pdFAIL;
EventBits_t uxBits;
/* Void the unused args */
FSP_PARAMETER_NOT_USED(p_args);
uxBits = xEventGroupGetBitsFromISR (g_update_console_event);
if ((uxBits & (STATUS_UPDATE_FREQ_INFO)) != (STATUS_UPDATE_FREQ_INFO))
{
/* Cast, as compiler will assume calc is int */
g_board_status.led_frequency = (uint16_t) ((g_board_status.led_frequency + 1) % 3);
xResult = xEventGroupSetBitsFromISR(g_update_console_event, STATUS_UPDATE_FREQ_INFO, &xHigherPriorityTaskWoken);
/* Was the message posted successfully? */
if (pdFAIL != xResult)
{
/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context
switch should be requested. The macro used is port specific and will
be either portYIELD_FROM_ISR() or portEND_SWITCHING_ISR() - refer to
the documentation page for the port being used. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
基础任务:quad-spi flash和octo-spi flash配置及读写速度测试;DAC配置生成波形及性能测试。这里使用两根microUSB数据线连接到开发板。一根线连接到“USB FULL”口,用来烧写调试程序,一根线连接到板子下方的“DEBUG”口,用来通过串口与板子通讯。
依然是使用例程中的测试代码,具体代码在ospi_test.c中实现。
DAC输出。依然使用“_quickstart”例程。
1、添加DAC功能,这里DAC输出使用P014管脚,直接设置会报错,原因是P014管脚已经被ADC0使用了,找到ADC0的设置,取消P014的使用,就可以了。
参考着老师的视频讲解,初始化DAC的使用。
在gpt_blue_callback方法中,添加DAC的调用,这里创建了一个锯齿波。设置一个变量,变量累加。因为“R_DAC_Write”最大输入值为4095.所以将变量自加,当值大于等于4095时就归零。使用示波器观察波形输出。
进阶任务:示例程序中新增命令打印信息。在“menu_main.c ”文件中找到例程的菜单,添加一行自定义菜单。
编写自定义菜单对应方法。
test_fn follow_me(void)
{
int8_t c = -1;
sprintf (s_print_buffer, "%s%s", gp_clear_screen, gp_cursor_home);
print_to_console((void*)s_print_buffer); //清屏
sprintf (s_print_buffer, "%s", "Follow me Season 2 Issue 3");
print_to_console((void*)s_print_buffer);
while (CONNECTION_ABORT_CRTL != c)
{
c = input_from_console ();
if ((MENU_EXIT_CRTL == c) || (CONNECTION_ABORT_CRTL == c))
{
break;
}
}
xEventGroupClearBits (g_update_console_event, STATUS_DISPLAY_MENU_KIS);
return (0);
}
然后调试运行,就可以看见自己增加的菜单了。
扩展任务:设计一个类似信号发生器功能的例程。可在示例程序上修改。通过命令或按键,设置DAC输出波形,可通过flash存储历史波形等信息。
这里简单地设计了两个波形:锯齿波、方波。通过DAC输出。使用外接示波器来做观察。模仿着例程设计一个菜单,显示用户可以选择的选项。选择1,显示锯齿波;选择2,显示方波。还有“w”、“r”两个选项,用户选择“w”则将当前显示的波形类型写入flash中。选择“r”则从flash中读取已经保存了的波形类型,并且通过DAC输出。
首先,在例程的基础上添加一个DAC输出波形的方法。通过一个全局变量来判断,输出的波形是锯齿波还是方波。
void DAC_Wave(char wavetype)
{
static int value=0;
if(wavetype==1) R_DAC_Write(&g_dac0_ctrl, value);//锯齿波
if(wavetype==2) R_DAC_Write(&g_dac0_ctrl, value > 2050 ? 4095 : 0);//方波
value=value+20;
if(value>=4095) value=0;
}
然后启动一个新菜单,通过键盘与用户交互,可以选择熟人波形类型,或者读写flash。这里使用了汉字输出,但在串口终端上实际输出的效果不太好,汉字的最后部分总是显示不全。
test_fn follow_me(void)
{
int8_t c = -1;
uint16_t buffer[1];
sprintf (s_print_buffer, "%s%s", gp_clear_screen, gp_cursor_home);
print_to_console ((void*) s_print_buffer); //清屏
sprintf (s_print_buffer, "%s", "波形控制:\r\n1:三角波 \r\n2:方波 \r\nr: 从flash存储读取\r\nw: 写入到flash存储\r\n");
print_to_console ((void*) s_print_buffer);
while (CONNECTION_ABORT_CRTL != c)
{
c = input_from_console ();
if ((MENU_EXIT_CRTL == c) || (CONNECTION_ABORT_CRTL == c))
{
break;
}
if (0 != c)
{
/* Cast, as compiler will assume calc is int */
sprintf (s_print_buffer, "select %c \r\n", c);
print_to_console ((void*) s_print_buffer);
if ('1' == c)
wavetype = 1; //输入1 选择三角波
if ('2' == c)
wavetype = 2; //输入2 选择方波
if ('r' == c) //输入r 读取保存的参数
{
readFromFlash (buffer);
sprintf (s_print_buffer, "read from buf %d \r\n", buffer[0]);
print_to_console ((void*) s_print_buffer);
// if (buffer[0] == '1' || buffer[0] == '2')
wavetype = buffer[0];
}
if ('w' == c) //输入w 保存方波类型参数
{
buffer[0] = wavetype;
saveToFlash (buffer);
}
}
}
xEventGroupClearBits (g_update_console_event, STATUS_DISPLAY_MENU_KIS);
return (0);
}
最后添加两个函数,用来读写flash。因为需要写入和读取的数据只有一个波形类型的数据,只需要一个整形数据即可。
void saveToFlash(uint16_t *buffer)
{
fsp_err_t err = FSP_SUCCESS;
uint32_t page_write_count = 0;
uint8_t *p_mem_addr = (uint8_t*) QSPI_DEVICE_START_ADDRESS;
spi_flash_protocol_t current_spi_mode;
/* Cast to req type */
p_mem_addr = (uint8_t*) QSPI_DEVICE_START_ADDRESS;
// err = R_QSPI_Open(&g_qspi_ctrl, &g_qspi_cfg);
// if (FSP_SUCCESS != err)
// {
// sprintf(s_print_buffer, "Failed to open QSPI module\r\n");
// return;
// }
/* initialise the QSPI, and change mode to that set in FSP */
err = qpi_init ();
if (FSP_SUCCESS == err)
{
/* The comms mode has changed. So if recovering, this new mode required */
current_spi_mode = g_qspi_cfg.spi_protocol;
}
/* 擦除 QSPI 的指定扇区 */
err = R_QSPI_Erase (&g_qspi_ctrl, p_mem_addr, SECTOR_SIZE);
if (FSP_SUCCESS != err)
{
sprintf (s_print_buffer, "Failed to erase QSPI flash\r\n");
return;
}
/* 等待擦除完成 */
err = get_flash_status ();
if (FSP_SUCCESS != err)
{
sprintf (s_print_buffer, "Failed to get flash status after erase\r\n");
return;
}
err = R_QSPI_Write (&g_qspi_ctrl, &buffer[0], p_mem_addr, 1);
if (FSP_SUCCESS != err)
{
sprintf (s_print_buffer, "Failed to write data to QSPI flash\r\n");
}
else
{
err = get_flash_status ();
if (FSP_SUCCESS != err)
{
sprintf (s_print_buffer, "Failed to get flash status after write\r\n");
}
}
// err = R_QSPI_Close(&g_qspi_ctrl);
// if (FSP_SUCCESS != err)
// {
// sprintf(s_print_buffer, "Failed to close QSPI module\r\n");
// }
/* close QSPI module */
deinit_qspi (current_spi_mode);
}
void readFromFlash(uint16_t *buffer)
{
fsp_err_t err = FSP_SUCCESS;
uint32_t page_read_count = 0;
uint8_t *p_mem_addr = (uint8_t*) QSPI_DEVICE_START_ADDRESS;
spi_flash_protocol_t current_spi_mode;
/* The comms mode of the FLASH device is EXTENDED_SPI by default */
current_spi_mode = SPI_FLASH_PROTOCOL_EXTENDED_SPI;
/* initialise the QSPI, and change mode to that set in FSP */
err = qpi_init ();
if (FSP_SUCCESS == err)
{
/* The comms mode has changed. So if recovering, this new mode required */
current_spi_mode = g_qspi_cfg.spi_protocol;
}
memcpy (&buffer[0], p_mem_addr, 1);
deinit_qspi (current_spi_mode);
}
心得体会:非常开心参加Follow me活动,这次活动推出的瑞萨的EK-RA6M5开发板功能超级强悍。但与此同时,掌握开发板的开发方法也相当的困难。好在有老师在视频课中事无巨细地耐心讲解,通过反复观看老师的视频,总算是入门了这块开发板,但是远远没有能发挥出这块开发板的能力,期待着后续看到各位老师优秀的作品。
- 2024-11-17
-
加入了学习《FollowMe 第二季: 1 Adafruit Circuit Playground Express及任务讲解》,观看 Adafruit Circuit Playground Express 及任务讲解
- 2024-11-15
-
回复了主题帖:
小米的四电机系统的圆规掉头、原地掉头,算法实现上难吗?
有现成的运动模型。能够单独控制4个轮子旋转方向和转速就可以原地旋转。但是针对非理想环境要如何处理,4个轮子所处的地面摩擦力相差较大时,需要额外增加角速度传感器来进行闭环处理。
-
加入了学习《鸿蒙 HarmonyOS NEXT星河版零基础入门到实战》,观看 ArkTS-认识和存储数据
- 2024-11-06
-
回复了主题帖:
想搞自动驾驶小车,在B站看到一个成本300的,在犹豫中
nmg 发表于 2024-11-6 15:52
扫地机器人的地图建模应该用的不是激光雷达吧,这个激光雷达是不是挺贵的
海鲜市场整个二手的,很便宜的。小50拿下!