当前位置: 首页 > 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…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如&#xff1a…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...