当前位置: 首页 > news >正文

通用树查找算法

        想要一个树形控件来显示数据,却发现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的遍历,处理起来就异常简单了。关键是,复杂度也很理想,这个最后分析。

具体算    法描述如下:

  1. 首先,nodes为包含节点的列表。

  2. 初始时nodes里仅包含根节点。

  3. 如果nodes列表为空,返回#f;

  4. 如果nodes列表不为空,取列表首值(节点):

  5. 如果该值是指定节点,返回该节点;

  6. 如果该值不是指定节点,检查其子节点:

  7. 如果有子节点,将子节点加入到nodes列表里,并重复3-6对nodes向后搜索;

  8. 如果没有子节点,重复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…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

如何为服务器生成TLS证书

TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...