KD树+BBF+KNN使用C#实现(1)

       最近研究了一下KD树,以及在此基础之上进行的改进BBF方法,以及如何利用BBF进行KNN。当然我还是主要参照很厉害的人物文章,代码利用C#实现了而已。

       在这里对我帮助最大的网址如下:

http://blog.csdn.net/v_july_v/article/details/8203674     这篇文章主要讲的就是K近邻,距离度量,KD树,以及BBF算法。里面讲的很详细,里面大部分都是伪代码,可能实现起来还是有一定的难度。在我几天的研究之下,实现了文章中所说的方法,当然也同时借鉴了第三个网址中讲解的,主要是通过那个网址中的代码来实现。

http://www.cnblogs.com/eyeszjwang/articles/2437706.html 这个文章中主要讲解的是KD树的优化查找方法BBF,文章中也同时实现了K近邻的查找,虽然第一个网址中的代码页讲解了实现过程,但是看起来还是比较晦涩的。

http://www.leexiang.com/kd-tree 这个文章中讲解了KD的构造过程,以及最近邻查找的实现过程,这篇文章对于我前期实现工作帮助特别大。

当我我希望研究这个算法的还是要详细的阅读这几篇文章。


下面我就贴出我的代码:

1、准备工作

public class Node
    {
        public Train point{get;set;}     //节点信息
        public Node leftNode { get; set; }   //左子树
        public Node righNode { get; set; }   //右子树
        public int split { get; set; }       //分割的方向轴序号
        public Node parent { get; set; }     //父节点
        public List<Train> range { get; set; }   //空间节点
    }

    public class Train
    {
        public float positionX { get; set; }
        public float positionY { get; set; }
        public float positionZ { get; set; }
        public Int32 AvgRssi { set; get; }
    }

    public class PriorityList
    {
        public Node node { get; set; }
        public float priority { get; set; }
    }

2、初始化数据

private void GenerareData()
        {
            lsTrain.Add(new Train() { positionX = 2, positionY = 3, positionZ = 0 });
            lsTrain.Add(new Train() { positionX = 5, positionY = 4, positionZ = 0 });
            lsTrain.Add(new Train() { positionX = 9, positionY = 6, positionZ = 0 });
            lsTrain.Add(new Train() { positionX = 9, positionY = 8, positionZ = 0 });
            lsTrain.Add(new Train() { positionX = 4, positionY = 7, positionZ = 0 });
            lsTrain.Add(new Train() { positionX = 8, positionY = 1, positionZ = 0 });
            lsTrain.Add(new Train() { positionX = 7, positionY = 2, positionZ = 0 });
        }


其中
private List<Train> lsTrain = new List<Train>();


3、建造KD树

Node root = CreatKDTree(lsTrain);

下面我们看看CreatKDTree()函数:

private Node CreatKDTree(List<Train> train)
        {
            //创建节点
            Node node = new Node();
            node.range = train;

            if (train.Count == 1)
            {
                //只有一个节点时即为叶节点,直接返回叶节点
                node.split = 0;     //默认为X方向轴分割
                node.point = train[0];
                node.leftNode = null;
                node.righNode = null;
                return node;
            }

            int axis = GetAxis(train);
            Train splitNode = GetSplitPoint(train, axis);

            train.Remove(splitNode);    //新的数据空间

            //获取左子树的数据空间,即比splitNode在axis方向上小的数据
            List<Train> leftTreeRange = this.LeftTreeRange(train, splitNode, axis);
            //获取右子树的数据空间,即比splitNode在axis方向上大的数据
            List<Train> rightTreeRange = this.RightTreeRange(train, splitNode, axis);


            node.split = axis;
            node.point = splitNode;

            //子树不为空则进行下一层递归
            if (leftTreeRange.Count == 0)
                node.leftNode = null;
            else
                node.leftNode = this.CreatKDTree(leftTreeRange);
            if (rightTreeRange.Count == 0)
                node.righNode = null;
            else
                node.righNode = this.CreatKDTree(rightTreeRange);

            return node;
        }

创建KD树的过程主要使用的是递归的方法:

首先我们计算每个维度上的方差,方差的大小说明了在这个维度上的点的分散程度。我们选择维度中方差最大的作为我们的分裂维度。

private int GetAxis(List<Train> train)
        {
            //计算方差,选择方向轴

            int axis = -1;  //坐标轴,0表示X轴,1表示Y轴,2表示Z轴

            float xDemonAvg = 0, yDemonAvg = 0, zDemonAvg = 0;   //定义了三个个维度的平均值
            foreach (var tmp in train)
            {
                xDemonAvg += tmp.positionX * 1.0f;
                yDemonAvg += tmp.positionY * 1.0f;
                zDemonAvg += tmp.positionZ * 1.0f;
            }
            //计算均值
            xDemonAvg = xDemonAvg / train.Count;
            yDemonAvg = yDemonAvg / train.Count;
            zDemonAvg = zDemonAvg / train.Count;

            //计算方差
            double xS2 = 0, yS2 = 0, zS2 = 0;    //初始化三个轴的方差
            foreach (var tmp in train)
            {
                xS2 += Math.Pow(tmp.positionX - xDemonAvg, 2);
                yS2 += Math.Pow(tmp.positionY - yDemonAvg, 2);
                zS2 += Math.Pow(tmp.positionZ - zDemonAvg, 2);
            }
            xS2 = xS2 / train.Count;
            yS2 = yS2 / train.Count;
            zS2 = zS2 / train.Count;

            if (xS2 >= yS2 && xS2 >= zS2)
            {
                axis = 0;
            }
            else if (yS2 > xS2 && yS2 > zS2)
            {
                axis = 1;
            }
            else
                axis = 2;

            return axis;
        }

其次、当我选择好这个维度之后,我们需要在这个维度上将这个空间上的所有点按照升序排列。当然排序算法有很多,不同的排序的算法效率也是不同的,这里我们没有关注效率问题,选择的是快速排序算法,这个算法就是List.Sort()方法的内部实现。

private Train QuickSort(List<Train> train, int axis)
        {
            if (0 == axis)
            {
                train.Sort(this.CompareTrainX);
            }
            else if (1 == axis)
            {
                train.Sort(this.CompareTrainY);
            }
            else
                train.Sort(this.CompareTrainZ);
            return train[train.Count / 2];
        }
        #region 定义比较器
        private int CompareTrainX(Train trainFirst, Train trainSecond)
        {
            if (trainFirst.positionX == trainSecond.positionX)
            {
                return 0;
            }
            else if (trainFirst.positionX < trainSecond.positionX)
            {
                return -1;
            }
            else
                return 1;
        }
        private int CompareTrainY(Train trainFirst, Train trainSecond)
        {
            if (trainFirst.positionY == trainSecond.positionY)
            {
                return 0;
            }
            else if (trainFirst.positionY < trainSecond.positionY)
            {
                return -1;
            }
            else
                return 1;
        }
        private int CompareTrainZ(Train trainFirst, Train trainSecond)
        {
            if (trainFirst.positionZ == trainSecond.positionZ)
            {
                return 0;
            }
            else if (trainFirst.positionZ < trainSecond.positionZ)
            {
                return -1;
            }
            else
                return 1;
        }
        #endregion

当我们将选定的分裂维度上的控件数据进行了排序之后,我们选择最中间的数据点最为树或者子树的跟节点。即:
Train splitNode = GetSplitPoint(train, axis);

当我们我们确定了分裂点之后,下面要完成的就是子空间划分,即将空间中的划分为左右子空间,即:

//获取左子树的数据空间,即比splitNode在axis方向上小的数据
            List<Train> leftTreeRange = this.LeftTreeRange(train, splitNode, axis);
            //获取右子树的数据空间,即比splitNode在axis方向上大的数据
            List<Train> rightTreeRange = this.RightTreeRange(train, splitNode, axis);

划分好子空间之后分别对子空间进行递归调用CreatKDTree()函数,进行子空间的KD树构造,递归的结束标志是子空间没有数据,即:

//子树不为空则进行下一层递归
            if (leftTreeRange.Count == 0)
                node.leftNode = null;
            else
                node.leftNode = this.CreatKDTree(leftTreeRange);
            if (rightTreeRange.Count == 0)
                node.righNode = null;
            else
                node.righNode = this.CreatKDTree(rightTreeRange);

这样我们就完成了KD树的构造过程。

代码下载


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

踏雪_无痕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值