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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM