通用树查找算法
想要一个树形控件来显示数据,却发现Racket的GUI库竟然没有提供这个控件。既然没有,那就自己手搓一个吧。没想到,在做这个控件中竟然有了新发现!
树形控件有一个功能是查找树中指定的节点。这就是接下来的故事的起点。
1 找外援
不想动脑筋,就找网络,网上一般都是二叉树、红黑树啥的,不是我需要的;找AI帮我写一个,如下:
问:用Racket写一个通用的树查找算法。
AI答:
以下是使用Racket编写的通用树查找算法:
#lang racket (define (tree? x) (and (pair? x) (not (null? x)))) (define (search-tree t target) (cond ((null? t) #f) ((equal? (car t) target) t) (else (+ 1 (search-tree (cdr t) target))))) (define tree '(("A" "B" "C") ("D" "E" "F") ("G" "H" "I"))) (displayln "Tree:") (tree-walk tree tree-root tree) (displayln "Search for 'D':") (search-tree tree 'D)
这个程序定义了一个辅助函数 ‘tree?‘,用于检查一个值是否为树。然后,它定义了 ‘search-tree‘ 函数,该函数接受一个树和目标值作为参数,并返回找到的目标节点或 "#f"(表示未找到)。最后,它创建了一个名为“tree”的示例树,并使用“tree-walk”函数遍历它。然后,它在树中搜索目标值“D”,并将结果打印到控制台上。
显然,AI也不是救世主!还是开动自己的思想解决问题吧。
2 我的思考
树的根节点、子节点、叶子节点的处理属于基本操作,不用过多考虑。重点是不确定的子节点数量,如何处理?
以下是一个通用树节点的结构:
;树节点结构: (struct node (id ;这里设定id值为数字。 value ;节点值。 children ;子节点,考虑用列表来存放。 ))
我实际要使用的树节点比这个要复杂一些,这里我就把这个结构来凑合作为例子样本吧。
如上面所说,重点就是children这个列表了。一般会考虑采用"for-each"遍历列表元素,然后用递归方式对每个元素的children继续以上过程,直到找到需要的值或遍历整个树为止。
但这样有一个严重的问题,这样的每次递归都会出现等待下一级递归结束后才能继续列表元素处理的问题,就会出现著名的低效递归的问题——因为这样递归在执行时需要系统提供隐式栈来实现递归,造成效率低效,甚至填满内存的严重问题(当然现在内存一般足够大,还不至于因内存溢出造成崩溃)。
解决办法是采用尾递归。难点还是在children列表上。
我找到一个办法——将列表与树结合起来进行尾递归,算法原理如下。
3 算法原理
假设我们有一个查找函数"find-node",其参数有两个——"tree"为要查找的树;"id"为查找的关键值。
在"find-node"中定义一个闭包"find-node/in",它来实现树的遍历查找,这也是常规操作。
为了实现尾递归,为"find-node/in"设定一个参数nodes作为存放树节点的子节点的列表(根节点是这个列表参数的初始值)。然后挨个搜索树的各个节点,如果遇到节点有子节点时,就把这个节点的children添加到nodes的后面。这样就把对树的搜索和对子节点列表的遍历转换为对列表nodes的遍历,处理起来就异常简单了。关键是,复杂度也很理想,这个最后分析。
具体算 法描述如下:
-
首先,nodes为包含节点的列表。
-
初始时nodes里仅包含根节点。
-
如果nodes列表为空,返回#f;
-
如果nodes列表不为空,取列表首值(节点):
-
如果该值是指定节点,返回该节点;
-
如果该值不是指定节点,检查其子节点:
-
如果有子节点,将子节点加入到nodes列表里,并重复3-6对nodes向后搜索;
-
如果没有子节点,重复3-6对nodes向后搜索。
注意,以上算法描述中还需要考虑一个特例,就是根节点不存在(一般设置为"#f")时的情况。为了处理这个特例,我们在取出列表首值后首先对其做一个判断——是否为未设置节点值的根节点。
4 实现树查找
根据上边的算法原理,我们把代码写出来,如下:
;定义取得节点值函数: ;tree为需要搜索的树,id为节点标识。 (define (find-node-version-1 tree id) (define (find-node/in nodes) (cond ;nodes列表为空,返回#f: [(empty? nodes) #f] ;值(节点)#f,即树根未初始化,树不存在: [(not (car nodes)) #f] ;nodes列表首值(节点)即为指定节点,返回节点: [(= (node-id (car nodes)) id) ;(display-node-value (car nodes)) (car nodes)] ;如果nodes首值不是指定节点,且其子列表为空: [(empty? (node-children (car nodes))) ;(display-node-value (car nodes)) (find-node/in (cdr nodes))] [else ;(display-node-value (car nodes)) (find-node/in (append (cdr nodes) (node-children (car nodes))))])) (find-node/in (list tree)))
5 测试
现在手搓一棵树并放到测试模块中来做测试。如下:
(module+ test ;测试空树: (define tree/empty #f) (find-node tree/empty 3) ;测试自定义树: (define tree/user (node 0 "t-0" (list (node 1 "t-1" '()) (node 2 "t-2" (list (node 3 "t-3" (list (node 4 "t-4" '()))) (node 5 "t-5" '()) (node 6 "t-6" (list (node 7 "t-7" '()) (node 8 "t-8" '()))))) (node 9 "t-9" (list (node 10 "t-10" (list (node 11 "t-11" '()))) (node 12 "t-12" '()))) (node 13 "t-13" (list (node 14 "t-14" (list (node 15 "t-15" (list (node 16 "t-16" '())))))))))) (let* ([id 13] [n (find-node-version-1 tree/user id)]) (display (format "查找结果为:id=~a,value=”~a“\n" id (node-value n)))) )
这棵树如果在手机这种移动设备上看可能有点乱。如果用图片显示应该是这样的:
运行程序,得到的结果是:
查找结果为:id=13,value=”t-13“
6 检查搜索路径
我们来看看程序的搜索路径。
为了实现这个功能,我再定义一个显示函数,以便显示出程序搜索的每一个节点情况。如下:
;显示节点值(用于测试): (define (display-node-value node) (display (format "id=~a,value=”~a“\n" (node-id node) (node-value node))))
也就是上边程序中被注释掉的函数。现在我们把它的注释去掉。再运行程序,得到以下结果:
id=0,value=”t-0“
id=1,value=”t-1“
id=2,value=”t-2“
id=9,value=”t-9“
id=13,value=”t-13“
哦,这不是遍历树的广度优先算法吗!这的确是遍历树的广度优先算法。
我们可以把这个算法独立出来用单独函数表示。如下:
;广度优先搜索: (define (find-node/width nodes id) (cond ;nodes列表为空,返回#f: [(empty? nodes) #f] ;值(节点)#f,即树根未初始化,树不存在: [(not (car nodes)) #f] ;nodes列表首值(节点)即为指定节点,返回节点: [(= (node-id (car nodes)) id) ;(display-node-value (car nodes)) (car nodes)] ;如果nodes首值不是指定节点,且其子列表为空: [(empty? (node-children (car nodes))) ;(display-node-value (car nodes)) (find-node/width (cdr nodes) id)] [else ;(display-node-value (car nodes)) (find-node/width (append (cdr nodes) (node-children (car nodes))) id)]))
相应地,查找函数就表示为:
;定义取得节点值函数: ;tree为需要搜索的树,id为节点标识。 (define (find-node tree id) ;广度优先搜索指定节点: (display (format "广度优先搜索指定节点:\n")) (find-node/width (list tree) id))
在测试模块中添加如下测试代码:
(let* ([id 13] [n (find-node tree/user id)]) (display (format "查找结果为:id=~a,value=”~a“\n" id (node-value n))))
运行程序,结果如下:
广度优先搜索指定节点:
id=0,value=”t-0“
id=1,value=”t-1“
id=2,value=”t-2“
id=9,value=”t-9“
id=13,value=”t-13“
与上边一样的结果。
那么问题来了,如果要实现深度优先算法,怎么做呢?
7 深度优先算法
从上边的算法原理来理解,如果在将子节点与现有nodes列表合并时,不是将子节点放到nodes列表之后,而是放到nodes列表之前——也就是说优先搜索子节点内容——就可以实现深度优先算法了。实现如下:
;深度优先搜索: (define (find-node/depth nodes id) (cond ;nodes列表为空,返回#f: [(empty? nodes) #f] ;值(节点)#f,即树根未初始化,树不存在: [(not (car nodes)) #f] ;nodes列表首值(节点)即为指定节点,返回节点: [(= (node-id (car nodes)) id) ;(display-node-value (car nodes)) (car nodes)] ;如果nodes首值不是指定节点,且其子列表为空: [(empty? (node-children (car nodes))) ;(display-node-value (car nodes)) (find-node/depth (cdr nodes) id)] [else ;(display-node-value (car nodes)) (find-node/depth (append (node-children (car nodes)) (cdr nodes)) id)]))
把深度优先算法添加到"find-node"函数中,如下:
;定义取得节点值函数: ;tree为需要搜索的树,id为节点标识。 (define (find-node tree id) ;广度优先搜索指定节点: (display (format "广度优先搜索指定节点:\n")) (find-node/width (list tree) id) ;深度优先搜索指定节点: (display (format "深度优先搜索指定节点:\n")) (find-node/depth (list tree) id))
运行程序,得到如下结果:
广度优先搜索指定节点:
id=0,value=”t-0“
id=1,value=”t-1“
id=2,value=”t-2“
id=9,value=”t-9“
id=13,value=”t-13“
深度优先搜索指定节点:
id=0,value=”t-0“
id=1,value=”t-1“
id=2,value=”t-2“
id=3,value=”t-3“
id=4,value=”t-4“
id=5,value=”t-5“
id=6,value=”t-6“
id=7,value=”t-7“
id=8,value=”t-8“
id=9,value=”t-9“
id=10,value=”t-10“
id=11,value=”t-11“
id=12,value=”t-12“
id=13,value=”t-13“
从上边的运行结果来看,广度优先算法比深度优先算法更快找到了指定的节点。当然,仅是对于这里给的数据样本及指定的id来说的。比如,如果id=8,运行结果就会出现深度优先算法更快找到结果的情况。
8 时间复杂度与空间复杂度
从以上分析和运行情况可以直观看出,本查找算法的时间复杂度与空间复杂度均为O(n),而且是稳定算法。
注:以上内容采用Racket语言的Scribble编辑并编译生成。
相关文章:

通用树查找算法
想要一个树形控件来显示数据,却发现Racket的GUI库竟然没有提供这个控件。既然没有,那就自己手搓一个吧。没想到,在做这个控件中竟然有了新发现! 树形控件有一个功能是查找树中指定的节点。这就是接下来的故事的起点。 1 找外援 不…...
Flutter 中的 TableCell 小部件:全面指南
Flutter 中的 TableCell 小部件:全面指南 Flutter 是一个功能强大的 UI 框架,由 Google 开发,允许开发者使用 Dart 语言构建跨平台的移动、Web 和桌面应用。在 Flutter 的丰富组件库中,TableCell 是一个用于创建表格单元格的组件…...

clickhouse学习笔记(一)入门与安装
目录 一 、入门 简介 核心特性包括 1.1 列式存储 1.2 原生压缩 1.3 向量化执行引擎 1.4 DBMS 功能 1.5 分布式处理 1.6 高吞吐写入能力 1.7 实时分析 1.8 SQL支持 1.9 高度可扩展 1.10 数据分区与线程级并行 1.11 应用场景 1.12 不适用场景 二、ClickHouse单机版…...

【JavaEE精炼宝库】多线程(4)深度理解死锁、内存可见性、volatile关键字、wait、notify
目录 一、死锁 1.1 出现死锁的常见场景: 1.2 产生死锁的后果: 1.3 如何避免死锁: 二、内存可见性 2.1 由内存可见性产生的经典案例: 2.2 volatile 关键字: 2.2.1 volatile 用法: 2.2.2 volatile 不…...

使用Ollama+OpenWebUI部署和使用Phi-3微软AI大模型完整指南
🏡作者主页: 点击! 🤖AI大模型部署与应用专栏:点击! ⏰️创作时间:2024年6月6日23点50分 🀄️文章质量:96分 欢迎来到Phi-3模型的奇妙世界!Phi-3是由微软…...

k8s的ci/cd实践之旅
书接上回k8s集群搭建完毕,来使用它强大的扩缩容能力帮我们进行应用的持续集成和持续部署,整体的机器规划如下: 1.192.168.8.156 搭建gitlab私服 docker pull gitlab/gitlab-ce:latest docker run --detach --hostname 192.168.8.156 --publ…...

笔记96:前馈控制 + 航向误差
1. 回顾 对于一个 系统而言,结构可以画作: 如果采用 这样的控制策略,结构可以画作:(这就是LQR控制) 使用LQR控制器,可以通过公式 和 构建一个完美的负反馈系统; a a 但是有上…...
延时任务工具类
自定义工具类 package com.sxfoundation.task;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.task.TaskRejectedException; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.spri…...

springboot下载grpc编译文件,报错缺少protoc-gen-grpc-java:1.34.1:exe不存在
报错如图所示 [ERROR] Then, install it using the command: [ERROR] mvn install:install-file -DgroupIdio.grpc -DartifactIdprotoc-gen-grpc-java -Dversion1.34.1 -Dclassifierwindows-x86_64 -Dpackagingexe -Dfile/path/to/file [ERROR] [ERROR] Alternatively, if yo…...

【面试干货】 非关系型数据库(NoSQL)与 关系型数据库(RDBMS)的比较
【面试干货】 非关系型数据库(NoSQL)与 关系型数据库(RDBMS)的比较 一、引言二、非关系型数据库(NoSQL)2.1 优势 三、关系型数据库(RDBMS)3.1 优势 四、结论 💖The Begin…...
JAVA学习-练习试用Java实现“简化路径”
问题: 给定一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 / 开头),请将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身…...

STM32——ADC篇(ADC的使用)
一、ADC的介绍 1.1什么是ADC ADC(Analogto-Digital Converter)模拟数字转换器,是将模拟信号转换成数字信号的一种外设。比如某一个电阻两端的是一个模拟信号,单片机无法直接采集,此时需要ADC先将短租两端的电…...

(文章复现)基于主从博弈的售电商多元零售套餐设计与多级市场购电策略
参考文献: [1]潘虹锦,高红均,杨艳红,等.基于主从博弈的售电商多元零售套餐设计与多级市场购电策略[J].中国电机工程学报,2022,42(13):4785-4800. 1.摘要 随着电力市场改革的发展,如何制定吸引用户选择的多类型零售套餐成为提升售电商利润的研究重点。为…...
深度评价GPT-4o:探索人工智能的新里程碑
在人工智能领域,OpenAI的GPT系列自推出以来就备受瞩目。GPT-4o作为该系列的最新版本,无疑是迄今为止最为强大的一代。它不仅在技术性能上有了质的飞跃,而且在应用的广泛性和深度上都展现出了惊人的潜力。本文将从版本对比、技术能力、使用体验…...

Linux命令篇(六):vi/vim专项
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝您生活愉快! 文章目录 一、什么是vim二…...

Java 还能不能继续搞了?
金三银四招聘季已落幕,虽说行情不是很乐观,但真正的强者从不抱怨。 在此期间,我收到众多小伙伴的宝贵反馈,整理出132道面试题,从基础到高级,有八股文,也有对某个知识点的深度解析。包括以下几部…...

【日记】遇到了一个很奇怪的大爷(845 字)
正文 花了昨天和今天两天时间,把数据转移完了。这块 2T 的硬盘可以光荣退休了。目前是没什么存储焦虑了。 农发行净开发一些垃圾系统。今天没什么业务,但跟 ActiveX 斗智斗勇了一整天,最后实在搞不过 IE 浏览器。我也懒得管了,又不…...

Python 机器学习 基础 之 处理文本数据 【处理文本数据/用字符串表示数据类型/将文本数据表示为词袋】的简单说明
Python 机器学习 基础 之 处理文本数据 【处理文本数据/用字符串表示数据类型/将文本数据表示为词袋】的简单说明 目录 Python 机器学习 基础 之 处理文本数据 【处理文本数据/用字符串表示数据类型/将文本数据表示为词袋】的简单说明 一、简单介绍 二、处理文本数据 三、用…...

GAT1399协议分析(10)--视频定义及解析
一、官方定义 二、字段解析 VideoID 类型BasicObjectID 解析参考GAT1399协议分析(8)--ImageInfo字段详解-CSDN博客 InfoKind 采集类型...

【C语言】学生管理系统:完整模拟与实现
🌈个人主页:是店小二呀 🌈C语言笔记专栏:C语言笔记 🌈C笔记专栏: C笔记 🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 🔥引言 本篇文章为修改了在校期间实训报告,使用C…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...

sshd代码修改banner
sshd服务连接之后会收到字符串: SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢? 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头,…...

《信号与系统》第 6 章 信号与系统的时域和频域特性
目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...
字符串哈希+KMP
P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...