C# 通過KD樹進行距離最近點的查找.

来源:http://www.cnblogs.com/MaFeng0213/archive/2017/09/26/7598886.html
-Advertisement-
Play Games

本文首先介紹Kd-Tree的構造方法,然後介紹Kd-Tree的搜索流程及代碼實現,最後給出本人利用C#語言實現的二維KD樹代碼。這也是我自己動手實現的第一個樹形的數據結構。理解上難免會有偏差,敬請各位多多斧正。 ...


本文首先介紹Kd-Tree的構造方法,然後介紹Kd-Tree的搜索流程及代碼實現,最後給出本人利用C#語言實現的二維KD樹代碼。這也是我自己動手實現的第一個樹形的數據結構。理解上難免會有偏差,敬請各位多多斧正。

1. KD樹介紹

Kd-Tree(KD樹),即K-dimensional tree,是一種高維索引樹形數據結構,常用於在大規模的高維數據空間進行最鄰近查找和近似最鄰近查找。我實現的KD樹是二維的Kd - tree。目的是在點集中尋找最近點。參考資料是Kd-Tree的百度百科。並且根據百度百科的邏輯組織了代碼。

2. KD樹的數學解釋

3. KD樹的構造方法

這裡是用的二維點集進行構造Kd-tree。三維的與此類似。
樹中每個節點的數據類型:

    public class KDTreeNode
    {
        /// <summary>
        /// 分裂點
        /// </summary>
        public Point DivisionPoint { get; set; }

        /// <summary>
        /// 分裂類型
        /// </summary>
        public EnumDivisionType DivisionType { get; set; }

        /// <summary>
        /// 左子節點
        /// </summary>
        public KDTreeNode LeftChild { get; set; }

        /// <summary>
        /// 右子節點
        /// </summary>
        public KDTreeNode RightChild { get; set; }
    }

3.1 KD樹構造邏輯流程

  1. 將所有的點放入集合a中
  2. 對集合所有點的X坐標求得方差xv,Y坐標求得方差yv
  3. 如果xv > yv,則對集合a根據X坐標進行排序。如果 yv > xv,則對集合a根據y坐標進行排序。
  4. 得到排序後a集合的中位數m。則以m為斷點,將[0,m-2]索引的點放到a1集合中。將[m,a.count]索引的點放到a2的集合中(m點的索引為m-1)。
  5. 構建節點,節點的值為a[m-1],如果操作集合中節點的個數大於1,則左節點對[0,m-2]重覆2-5步,右節點為對[m,a.count]重覆2-5步;反之,則該節點為葉子節點。

3.2 代碼實現

private KDTreeNode CreateTreeNode(List<Point> pointList)
{
    if (pointList.Count > 0)
    {
        // 計算方差
        double xObtainVariance = ObtainVariance(CreateXList(pointList));
        double yObtainVariance = ObtainVariance(CreateYList(pointList));

        // 根據方差確定分裂維度
        EnumDivisionType divisionType = SortListByXOrYVariances(xObtainVariance,        yObtainVariance, ref pointList);

        // 獲得中位數
        Point medianPoint = ObtainMedian(pointList);
        int medianIndex = pointList.Count / 2;

        // 構建節點
        KDTreeNode treeNode = new KDTreeNode()
        {
            DivisionPoint = medianPoint,
            DivisionType = divisionType,
            LeftChild = CreateTreeNode(pointList.Take(medianIndex).ToList()),
            RightChild = CreateTreeNode(pointList.Skip(medianIndex + 1).ToList())
        };
        return treeNode;
    }
    else
    {
        return null;
    }
}

4. KD樹搜索方法

Kd-Tree的總體搜索流程先根據普通的查找找到一個最近的葉子節點。但是這個葉子節點不一定是最近的點。再進行回溯的操作找到最近點。

4.1 KD樹搜索邏輯流程

  1. 對於根據點集構建的樹t,以及查找點p.將根節點作為節點t進行如下的操作
  2. 如果t為葉子節點。則得到最近點n的值為t的分裂點的值,跳到第5步;如果t不是葉子節點,進行第3步
  3. 則確定t的分裂方式,如果是按照x軸進行分裂,則用p的x值與節點的分裂點的x值進行比較,反之則進行Y坐標的比較
  4. 如果p的比較值小於t的比較值,則將t指定為t的左孩子節點。反之將t指定為t的右孩子節點,執行第2步
  5. 定義檢索點m,將m設置為n
  6. 計算m與p的距離d1,n與m的距離d2。
  7. 如果d1 >= d2且有父節點,則將m的父節點作為m的值執行5步,若沒有父節點,則得到真正的最近點TN; 如果d1 < d2就表示n點不是最近點,執行第8步
  8. 若n有兄弟節點,則 n = n的兄弟節點;若n沒有兄弟節點,則 n = n的父節點。刪除原來的n節點。將m的值設置為新的n節點;執行第6步。

4.2 代碼實現

public Point FindNearest(Point searchPoint)
{
    // 按照查找方式尋找最近點
    Point nearestPoint = DFSSearch(this.rootNode, searchPoint);
    
    // 進行回溯
    return BacktrcakSearch(searchPoint, nearestPoint);
}


private Point DFSSearch(KDTreeNode node,Point searchPoint,bool pushStack = true)
{
    if(pushStack == true)
    {
        // 利用堆棧記錄查詢的路徑,由於樹節點中沒有記載父節點的原因
        backtrackStack.Push(node);
    }
    if (node.DivisionType == EnumDivisionType.X)
    {
       return DFSXsearch(node,searchPoint);
    }
    else
    {
       return DFSYsearch(node, searchPoint);
    }
}

private Point BacktrcakSearch(Point searchPoint,Point nearestPoint)
{
    // 如果記錄路徑的堆棧為空則表示已經回溯到根節點,則查到的最近點就是真正的最近點
    if (backtrackStack.IsEmpty())
    {
        return nearestPoint;
    }
    else
    {
        KDTreeNode trackNode = backtrackStack.Pop();
        
        // 分別求回溯點與最近點距查找點的距離
        double backtrackDistance = ObtainDistanFromTwoPoint(searchPoint,         trackNode.DivisionPoint);
        double nearestPointDistance = ObtainDistanFromTwoPoint(searchPoint, nearestPoint);
        
       if (backtrackDistance < nearestPointDistance)
       {
           // 深拷貝節點的目的是為了避免損壞樹
           KDTreeNode searchNode = new KDTreeNode()
           {
                DivisionPoint = trackNode.DivisionPoint,
                DivisionType = trackNode.DivisionType,
                LeftChild = trackNode.LeftChild,
                RightChild = trackNode.RightChild
            };
           nearestPoint = DFSBackTrackingSearch(searchNode, searchPoint);
      }
      // 遞歸到根節點
      return BacktrcakSearch(searchPoint, nearestPoint);
   }
}

5. 源碼交流

https://github.com/CreamMilk/C-Kd-Tree


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 部署單體應用程式意味著運行一個或多個相同副本的單個較大的應用程式。您通常會在每個伺服器上配置 N 個伺服器(物理或虛擬)並運行 M 個應用程式實例。單體應用程式的部署並不總是非常簡單,但它比部署微服務應用程式要簡單得多。 ...
  • EventBus主要是幹嘛使的,直接翻譯叫事件匯流排。 是觀察者模型的實現,利用它你既可以實現觀察者模型的業務場景,還可以基於它的事件驅動機制來實現應用程式內組件之間的解耦與通信。 我們來看看有EventBus的匯流排結構圖,如下: Rafy中的EventBus使用入口是基於上圖中Composer組件組 ...
  • 問題引出 這視乎是個完全不必要進行討論的話題,因為linq(這裡具體是linq to objects)本來就是針對集合類型的,數組類型作為集合類型的一種當然可以使用了。不過我還是想寫一下,這個問題源於qq群里一位朋友的提問:.net的數組類型都隱式繼承了Array類,該類是一個抽象類,並且實現了IE ...
  • 最近winform上使用ReportViewer做報表,因為之前沒弄過,所以遇到了很多問題,現在總結一下。 一、運行環境 .net環境:4.0 開發工具:vs2010 二、開發步驟 第一步,在winform窗體上添加ReportViewer控制項作為呈現報表的容器,重新命名為reportViewerT ...
  • 本篇作為技術分享系列的第三篇,詳細講一下手繪視頻中結合視頻的處理方式。 隨著近幾年短視頻和直播行業的興起,視頻成為了人們表達情緒和交流的一種重要方式,人們對於視頻的創作、編輯和分享有了更多的需求。而視頻的編輯、剪輯方式,也由過去需要藉助專業的視頻剪輯軟體,專業的視頻剪輯操作者操作,變為現在的普通用戶 ...
  • .NET Core 是.NET Framework的新一代跨平臺應用程式開發框架,是微軟在一開始發展時就開源的軟體平臺,ASP.NET Core 以控制台應用程式驅動其托管環境 Kestrel Server 以支持 ASP.NET Core 程式的運行。 ...
  • ADO.NET Entity Framework 是微軟以 ADO.NET 為基礎所發展出來的對象關係對應 (O/R Mapping) 解決方案,不僅支持SQL Server,還支持MySQL、Oracle等資料庫。接下來給大家講解EF6+MYSQL具體的配置流程,以及配置過程中一些常見錯誤的解決方... ...
  • 將String[]類型的Object類型,轉換為String[]類型: 使用 is 進行判斷 ob 是否為 string[] 類型。 將 string 類型轉換為 DateTime 類型: 註意: 使用 DateTime.TryParse(); 進行轉換判斷時,如果返回 true,強制轉換結果將傳入 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...