斯坦福CS106L-C++作业3——KDTree(k-dimensional tree)

参考资料:
斯坦福CS106L

算法目的:
在一个N维空间中找到某个点最近的一个点(距离用平方来衡量)。

算法:
算法的大致过程如下(记住还有一部分不一样),跟二叉搜索树差不多,比较的树跟每个节点进行比较来预测要向左还是向右,但是这里有N维,每一层从根节点开始一次是第0,1,2...N-1,0,1,2...个数字进行比较,图中粗体的数字就是那个节点要比较的数字,此数字要比左子树对应的数字(序号相同)大,比右子树对应的数字小或者相等。插入的时候也是一样的。


直觉:
以二叉树为例,每次把数据切分两半进行查找。


线性切分二维数据,随机选择一个数组,随机做一条直线,把数据且成两部分,不断地分割下去( binary space partitioning trees or BSP trees)。多维也是一样,超平面。


KDTree是上面的一种特殊情况,首先根节点是竖直切分平面,多次之后如下所示。



但是这样分并不能我们保证我们可以得到最近的点,如下图所示,绿色的点是目标点,如上进行切分,得到最近的点是跟其在同一个区域的灰色点,但是根据距离公式,蓝色的点才是最近的点。


但在另外一种情况下,我们可以确定跟绿点最近的点一定是根据上面规则划分的同一个区域内。我们在缩小范围的时候(假设是第二次画的横线),每次都会更新最小的距离r和点,我们知道距离更小的点只能是在目标点及r所形成的圆内,而如果我们知道圆又在我们缩小的这个区域里面(这个计算d = |b(i) – a(i)| > r 即可知道),那么横线所切的另外一个方向是不能有更近的点的(局部与整体的关系,圆外(整体)都不可能,更不用说横线之上(局部))。这里我们切了两刀(图中序号标出),假设我们知道最小的距离至少是r,切了两刀,但实际区域并没有大概减少至1/4,因为第一道切完,距离一算,发现包含圆的一部分,因此并不说这一刀的左边的黄色点可以排除,第二刀如上所述可以排除蓝色的点。


伪代码如下,除了最后两句,上面的都是在二分区域,最后两句是为了补救那些不可排除的区域。其中bestDist可以是在上面递归里面减少,不必保持不变。


进一步推广——寻找K个最近的点(k-NN search)
bounded priority queue (or BPQ for short):固定容量的队列,元素有一定的优先级,如果队列满了,新的元素优先级更高,则会有元素会被弹出,并塞入新元素,新的元素优先级不够高,则队列保持原状。


实现步骤:
1、实现KDTree的基本操作
主要是插入,规则跟二叉搜索树差不多。其他也只是数的一些相关操作。

2、实现kNN,主要是在lambda函数recursiveFun部分,这部分是上一张图片描述的算法实现的。
要注意一点是Lambda函数的回调调用,首先要提前声明,然后赋值的时候要把函数名也作为captures。

// ElemType kNNValue(const Point<N>& key, size_t k) const
    // Usage: cout << kd.kNNValue(v, 3) << endl;
    // ----------------------------------------------------
    // Given a point v and an integer k, finds the k points in the KDTree
    // nearest to v and returns the most common value associated with those
    // points. In the event of a tie, one of the most frequent value will be
    // chosen.
    ElemType kNNValue(const Point<N>& key, size_t k) const {
        BoundedPQueue<ElemType> q(k);
        double distance;

        //定义递归函数
        std::function<void(Node*)> recursiveFun;
        recursiveFun = [&recursiveFun,&q,&distance,&key](Node *node) {
            if(node==nullptr)
                return ;

            distance = 0;
            const Point<N>& currentKey = node->key;
            for(size_t i=0;i<N;i++) {
                distance += (key[i] - currentKey[i])*(key[i] - currentKey[i]);
            }
            q.enqueue(node->value,distance);

            Node* other;
            size_t index = (node->level) % N;
            if(key[index] < node->key[index]){
                recursiveFun(node->lc);
                other = node->rc;
            } else {
                recursiveFun(node->rc);
                other = node->lc;
            }

            if((!q.full()) || abs(key[index] - node->key[index]) < q.worst())
                recursiveFun(other);
        };

        //开始运行
        recursiveFun(root);

        // 统计每个ElemType类型变量出现频次
        map<ElemType,int> cntMap;
        ElemType elem;
        while(!q.empty()){
            elem = q.dequeueMin();
            if(cntMap.find(elem)==cntMap.end()){
                cntMap[elem] = 1;
            } else {
                cntMap[elem] += 1;
            }
        }

        //选出频次最大的
        ElemType maxElem;
        int Max = 0;
        for(auto pair:cntMap) {
            if(pair.second > Max){
                maxElem = pair.first;
                Max = pair.second;
            }
        }

        return maxElem;
    }

3、复制构造器和赋值符号
这里我一开始一次性分配全部内存可能会快一点。

Node *copyNodes(Node *buff,Node *node){
        if(node == nullptr)
            return nullptr;

        Node *NodeCopyed = buff + _cnt;
        _cnt ++;

        NodeCopyed->lc = copyNodes(buff,node->lc);
        NodeCopyed->rc = copyNodes(buff,node->rc);

        NodeCopyed->key = node->key;
        NodeCopyed->level = node->level;
        NodeCopyed->value = node->value;

        return NodeCopyed;
    };
    
    // KDTree(const KDTree& rhs);
    // KDTree& operator=(const KDTree& rhs);
    // Usage: KDTree<3, int> one = two;
    // Usage: one = two;
    // -----------------------------------------------------
    // Deep-copies the contents of another KDTree into this one.
    KDTree(const KDTree& rhs) {
        n_elems = rhs.n_elems;

        Node *buff = new Node[rhs.n_elems];
        _cnt = 0;
        root = copyNodes(buff,rhs.root);
    }

    KDTree& operator=(const KDTree& rhs){
        n_elems = rhs.n_elems;

        Node *buff = new Node[rhs.n_elems];
        _cnt = 0;
        root = copyNodes(buff,rhs.root);
        return *this;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,795评论 2 89
  • 1 序 2016年6月25日夜,帝都,天下着大雨,拖着行李箱和同学在校门口照了最后一张合照,搬离寝室打车去了提前租...
    RichardJieChen阅读 5,016评论 0 12
  • 三十余载音讯灭, 天高云远山水阔。 常记寒窗读书苦, 犹闻校场做操乐。 课间追逐相亙戏, 宿舍打闹同操戈。 弹指一...
    李小恩梦海幽兰阅读 289评论 0 1
  • 一只四线小县城的屌丝男,没事喜欢玩玩游戏,看看书,总想写点狗屁文字,算是对无聊生活的一种记录吧,那就从今天开始吧....
    0afbe7be6315阅读 114评论 0 0
  • 《香雪》 冬日漫天雪 唯有此香绝 疑为梦中境 缘自梅花约 《梅花雪》 我徒步 风花雪月夜 梦留痕幽幽青石阶...
    独孤一鸣阅读 565评论 44 47