Kd樹+BBF(最鄰近、次鄰近查詢)Python實現

 python2.7

import numpy as np

 構建Kd樹:

KD樹的構造 
一維的二叉查找樹很好構造,先對所有數據排序,然後每次取中值,把數據分成兩半,左半爲左子樹,右半爲右子樹;然後遞歸下去就好了。這樣可以保證構造出來的二叉樹是平衡的。 
KD樹處理的數據是多維的,因此每次劃分需要選定某一維作爲參考來劃分數據。選定後所有數據按這一維排序,然後劃分成左子樹,右子樹。參考維度的選定可以依次選,比如這一層以X維劃分,下一層就以Y維,如此循環反覆。更好的方法是每次選擇方差最大的那一維。只要劃分以後左右區域都還有數據,劃分就進行下去,直到按某個節點劃分完以後兩邊沒有數據點爲止。

# kd-tree每個結點中主要包含的數據結構如下
class KdNode(object):
    def __init__(self, dom_elt, split, left, right):
        self.dom_elt = dom_elt  # k維向量節點(k維空間中的一個樣本點)
        self.split = split  # 整數(進行分割維度的序號)
        self.left = left  # 該結點分割超平面左子空間構成的kd-tree
        self.right = right  # 該結點分割超平面右子空間構成的kd-tree


class KdTree(object):
    def __init__(self, data):
        k = len(data[0])  # 數據維度

        def CreateNode(split, data_set):  # 按第split維劃分數據集exset創建KdNode
            if not data_set:  # 數據集爲空
                return None
            # key參數的值爲一個函數,此函數只有一個參數且返回一個值用來進行比較
            # operator模塊提供的itemgetter函數用於獲取對象的哪些維的數據,參數爲需要獲取的數據在對象中的序號
            # data_set.sort(key=itemgetter(split)) # 按要進行分割的那一維數據排序
            data_set.sort(key=lambda x: x[split])
            split_pos = len(data_set) // 2  # //爲Python中的整數除法
            median = data_set[split_pos]  # 中位數分割點
            split_next = (split + 1) % k  # cycle coordinates

            # 遞歸的創建kd樹
            return KdNode(median, split,
                          CreateNode(split_next, data_set[:split_pos]),  # 創建左子樹
                          CreateNode(split_next, data_set[split_pos + 1:]))  # 創建右子樹

        self.root = CreateNode(0, data)  # 從第0維分量開始構建kd樹,返回根節點

BBF查詢:

#kdTree_bbf
class Prioritylist(object):
    def __init__(self,kdnode,priority):
        self.node=kdnode
        self.priority=priority

prioritylist=[]#存放Prioritylist p1

def InsertPriorityList(kdnode,priority):
    p1=Prioritylist(kdnode,priority)
    if len(prioritylist)==0:
        prioritylist.append(p1)
        return
    for i in range(len(prioritylist)):
        if prioritylist[i].priority>=priority:
            prioritylist.insert(i,p1)
            break
        else:
            prioritylist.append(p1)
            break

def RemovePriority(kdnode):
    for i in range(len(prioritylist)):
        if prioritylist[i].node.dom_elt==kdnode.dom_elt:
            prioritylist.pop(i)
            break

#優先級的計算,計算目標點和分割點之間的距離(某一維度),即優先級
def CalPriority(kdnode,target,split):
    return abs(kdnode.dom_elt[split]-target[split])

def CalDistance(vector1,vector2):
    return ((np.array(vector1)-np.array(vector2))**2).sum()**0.5


def BBFFindNearest(kdnode,target):
    nearest=kdnode
    sec_near=float("inf")
    priority=CalPriority(nearest,target,kdnode.split)
    InsertPriorityList(nearest,priority)
    top_node=None
    currentNode=None
    fir_dis=CalDistance(nearest.dom_elt,target)
    sec_dis=0
    while len(prioritylist)>0:
        top_node=prioritylist[0].node
        RemovePriority(top_node)
        while top_node!=None:
            if top_node.left!=None or top_node.right!=None:
                split=top_node.split
                if target[split]<=top_node.dom_elt[split]:
                    if top_node.right!=None:
                        priority=CalPriority(top_node.right,target,top_node.split)
                        InsertPriorityList(top_node.right,priority)
                    top_node=top_node.left
                else:
                    if top_node.left!=None:
                        priority=CalPriority(top_node.left,target,top_node.split)
                        InsertPriorityList(top_node.left,priority)
                    top_node=top_node.right
                currentNode=top_node
            else:
                currentNode=top_node
                top_node=None
            if currentNode!=None and (CalDistance(nearest.dom_elt,target)>CalDistance(currentNode.dom_elt,target)):
                sec_near=nearest
                nearest=currentNode
                fir_dis = CalDistance(nearest.dom_elt, target)
                sec_dis=CalDistance(sec_near.dom_elt,target)
            elif currentNode!=None and (CalDistance(nearest.dom_elt,target)<CalDistance(currentNode.dom_elt,target)):
                if sec_near==float("inf"):
                    sec_near=currentNode
                    sec_dis = CalDistance(sec_near.dom_elt, target)
                else:
                    if CalDistance(sec_near.dom_elt,target)>CalDistance(currentNode.dom_elt,target):
                        sec_near=currentNode
                        sec_dis=CalDistance(sec_near.dom_elt,target)
    return nearest,sec_near,fir_dis,sec_dis

測試:

# KDTree的前序遍歷
def preorder(root):
    print root.dom_elt
    if root.left:  # 節點不爲空
        preorder(root.left)
    if root.right:
        preorder(root.right)

if __name__=='__main__':
    data=[[2,3],[5,4],[9,6],[4,7],[8,1],[7,2]]
    kd=KdTree(data)
    preorder(kd.root)
    nearest,sec_near=BBFFindNearest(kd.root,[0,0])
    print 'The nearest point is:',nearest.dom_elt,',Distance is:',firdis
    print 'The second point is:',sec_near.dom_elt,',Distance is:',secdis

測試結果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章