0

0

如何在 Java 中基于 KD 树高效实现 M 近邻搜索(k-NN 扩展版)

花韻仙語

花韻仙語

发布时间:2026-01-19 09:55:11

|

152人浏览过

|

来源于php中文网

原创

如何在 Java 中基于 KD 树高效实现 M 近邻搜索(k-NN 扩展版)

本文详解如何在不依赖第三方库的前提下,基于自定义 kd 树结构,用 java 实现 `float[][] findmnearest(float[] point, int m)` 方法,支持返回距离查询点最近的 m 个样本坐标,涵盖剪枝策略、最大堆优化与递归回溯逻辑。

在单近邻(1-NN)搜索中,我们维护一个全局最优节点 best 和最小距离 bestDistance,通过轴对齐分割与超矩形剪枝高效遍历。但扩展到 M 近邻(M-NN) 时,核心挑战在于:
✅ 不再只需跟踪“当前最近”,而需动态维护 候选集 Top-M
✅ 剪枝条件必须升级——不能仅比较单点距离,而需判断「当前子树是否可能包含比当前第 M 近点更近的点」;
✅ 需避免重复访问或遗漏,尤其在回溯时需重新评估另一侧子树。

✅ 解决方案:最大堆 + 递归回溯(推荐)

使用 PriorityQueue 构建固定容量的最大堆(按欧氏距离平方排序),始终保留距离最小的 m 个点。堆顶即当前第 m 近的距离上限 maxHeapTopDistSq,用于关键剪枝:

import java.util.*;

public class KDTree {
    private static class KDNode {
        final float[] coords;
        KDNode left, right;
        int axis; // splitting axis (0, 1, ..., k-1)

        KDNode(float[] coords, int axis) {
            this.coords = coords.clone();
            this.axis = axis;
        }

        float distanceSq(KDNode other) {
            float sum = 0f;
            for (int i = 0; i < coords.length; i++) {
                float d = coords[i] - other.coords[i];
                sum += d * d;
            }
            return sum;
        }

        float getCoordinate(int dim) { return coords[dim]; }
    }

    private KDNode root;
    private final int k; // dimensionality

    public KDTree(int k) { this.k = k; }

    // Main M-NN method
    public float[][] findMNearest(float[] point, int m) {
        if (point == null || m <= 0 || root == null) 
            return new float[0][0];

        // Max-heap: store [distance^2, coordinates] → sort by distance^2 descending
        PriorityQueue<float[]> maxHeap = new PriorityQueue<>((a, b) -> 
            Float.compare(b[0], a[0]) // descending order
        );

        // Recursive search with pruning
        searchMNN(root, new KDNode(point, 0), 0, maxHeap, m);

        // Extract top m points (heap may contain < m if tree size < m)
        float[][] result = new float[maxHeap.size()][k];
        int i = 0;
        while (!maxHeap.isEmpty()) {
            float[] entry = maxHeap.poll();
            System.arraycopy(entry, 1, result[i++], 0, k); // skip dist at index 0
        }
        return result;
    }

    private void searchMNN(KDNode node, KDNode target, int depth, 
                          PriorityQueue<float[]> heap, int m) {
        if (node == null) return;

        int axis = depth % k;
        float distSq = node.distanceSq(target);
        float[] coords = node.coords;

        // Insert current node if heap not full, or replace worst if closer
        if (heap.size() < m) {
            float[] entry = new float[k + 1];
            entry[0] = distSq;
            System.arraycopy(coords, 0, entry, 1, k);
            heap.offer(entry);
        } else if (distSq < heap.peek()[0]) {
            heap.poll(); // remove worst
            float[] entry = new float[k + 1];
            entry[0] = distSq;
            System.arraycopy(coords, 0, entry, 1, k);
            heap.offer(entry);
        }

        // Determine which child is closer & visit first (better pruning chance)
        boolean goLeftFirst = (target.getCoordinate(axis) < node.getCoordinate(axis));
        KDNode nearChild = goLeftFirst ? node.left : node.right;
        KDNode farChild  = goLeftFirst ? node.right : node.left;

        // Visit near subtree first
        searchMNN(nearChild, target, depth + 1, heap, m);

        // Pruning: check if far subtree can contain better candidates
        float diff = target.getCoordinate(axis) - node.getCoordinate(axis);
        float diffSq = diff * diff;

        // If heap is not full, we MUST check far side (no pruning)
        // If heap is full, only explore far side if diffSq < heap's max distance^2
        if (heap.size() == m && diffSq < heap.peek()[0]) {
            searchMNN(farChild, target, depth + 1, heap, m);
        }
    }
}

⚠️ 关键注意事项

  • 距离平方代替开方:全程使用 distanceSq 避免 Math.sqrt() 的性能开销,排序与剪枝逻辑完全等价;
  • 堆容量控制:PriorityQueue 必须限制为最多 m 个元素,否则内存与时间复杂度失控;
  • 剪枝条件严格性:diffSq < heap.peek()[0] 是核心剪枝依据——它表示:当前分割超平面到查询点的距离平方,小于当前第 m 近点的距离平方,意味着远侧子树中仍可能存在更近点;
  • 空堆处理:若整棵树节点数 < m,最终结果自然少于 m 行,符合语义(无需补零或抛异常);
  • 线程安全:该实现非线程安全;如需并发调用,请为每次查询新建独立堆实例。

✅ 性能与验证建议

  • 时间复杂度:平均 O(log N + m log m)(N 为节点数),最坏 O(N);
  • 推荐单元测试覆盖:m=1(应与原 nearest 方法一致)、m=3、m > 树大小、边界点(如根节点本身);
  • 可视化调试:打印 visited 计数器对比 1-NN 与 M-NN 的访问节点数,验证剪枝有效性。

通过将单近邻的“全局最优”升级为“动态 Top-M 堆”,并强化剪枝阈值为堆顶距离,你就能在保持 KD 树经典结构的同时,稳健支撑多近邻检索需求——这正是工业级空间索引(如 Elasticsearch 向量搜索、FAISS 子模块)的核心思想之一。

Programming Helper
Programming Helper

AI代码自动生成器,在AI的帮助下更快地编程

下载

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
css中float用法
css中float用法

css中float属性允许元素脱离文档流并沿其父元素边缘排列,用于创建并排列、对齐文本图像、浮动菜单边栏和重叠元素。想了解更多float的相关内容,可以阅读本专题下面的文章。

595

2024.04.28

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

108

2025.10.23

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1030

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

612

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

334

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

235

2025.08.29

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

443

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

605

2023.08.10

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.4万人学习

C# 教程
C# 教程

共94课时 | 11.2万人学习

Java 教程
Java 教程

共578课时 | 81.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号