• 四叉树搜索附近的人


    现在很多的APP都有"附近的人"功能。

    粗略的思考一下,用户在登录的时候会将自己的位置信息告诉服务器,服务器会记录一份用户的位置信息列表。

    假设服务器里只有10个人,那么要找附近的人就很简单,只需写一个算距离的函数,然后依次遍历长度是10的位置信息列表,距离从近到远排序,返回排序后的列表即可。

    那么如果服务器里有1千万人呢,或者几亿人呢,比如微信。这时再从头到尾遍历(复杂度O(n))就不适合了。

    通常情况下会使用“四叉树”来构建这些大量的位置信息。

    1.四叉树

    顾名思义,四叉树是叶子节点最多为4的树结构。由于采用的树结构,复杂度则变成了树的深度 O(logn)了。

    2.地理位置和四叉树

    把全球的位置定义为一个矩形,用经度和纬度的二维数据来定位全球范围内的点。

    纬度范围[-90,90],经度范围[-180,180]。

    当登录服务器的人数越来越多时,超过一个约定的值,就将这个矩形分割成4个更小的矩形,将这些位置信息分发到更小的矩形里(比如分成太平洋,大西洋等)

    更小的矩形存储的信息又要超过约定的值了,就再分割。

    3.查找附近的点

    假设我在广州天河的华农的图书馆,想要找到附近的10个人的位置。

    那么首先需要从根节点(全球)遍历到天河区的节点,搜寻这个节点的列表里有没有10个人。

    如果不够10个人,则向上一层的广州节点请求,可能会在海珠区找到剩余的人。

    4.C++描述算法

    以上就是四叉树搜索位置的关键点,那么就简单用C++来编写代码描述这个算法。

    github:https://github.com/yao2yao/QuadTree.git

    我采用的是位置全部信息存储在叶子的四叉树。

    首先是核心的数据结构树的节点:

    struct QuadTreeNode
        {
            QuadTreeNode(){}
            Rect        rect;                    //节点代表的矩形区域
            std::vector<PosInfo> pos_array;        //节点中的位置信息
            int            child_num;                //节点的孩子节点数量
            QuadTreeNode *child[CHILD_NUM];        //指向孩子节点的指针
            int            depth;                    //节点的深度
        };

    创建一个节点:

    void QuadTree::CreateQuadTreeNode(int depth, Rect rect, QuadTreeNode *p_node)
    {
        p_node->depth = depth;
        p_node->child_num = 0;
        p_node->pos_array.clear();
        p_node->rect = rect;
        for (int i = 0; i < CHILD_NUM; ++i)  
        {  
            p_node->child[i] = NULL; 
        }
    }

    分割一个节点:

    这里采用均分法,即取x坐标和y坐标的一半划分。new四个节点,并且作为当前结点的4个孩子节点。

    void QuadTree::Split(QuadTreeNode *pNode)
    {
        int start_x = pNode->rect.lb_x;
        int start_y = pNode->rect.lb_y;
        int sub_width = (pNode->rect.rt_x - pNode->rect.lb_x) / 2;
        int sub_height = (pNode->rect.rt_y - pNode->rect.lb_y) / 2;
        int end_x = pNode->rect.rt_x;
        int end_y = pNode->rect.rt_y;
    
        QuadTreeNode *p_node0 = new QuadTreeNode;
        QuadTreeNode *p_node1 = new QuadTreeNode;
        QuadTreeNode *p_node2 = new QuadTreeNode;
        QuadTreeNode *p_node3 = new QuadTreeNode;
    
        CreateQuadTreeNode(pNode->depth + 1, Rect(start_x + sub_width, start_y + sub_height, end_x, end_y), p_node0);
        CreateQuadTreeNode(pNode->depth + 1, Rect(start_x, start_y + sub_height, start_x + sub_width, end_y), p_node1);
        CreateQuadTreeNode(pNode->depth + 1, Rect(start_x, start_y, start_x + sub_width, start_y + sub_height), p_node2);
        CreateQuadTreeNode(pNode->depth + 1, Rect(start_x + sub_width, start_y, end_x, start_y + sub_height), p_node3);
    
        pNode->child[0] = p_node0;
        pNode->child[1] = p_node1;
        pNode->child[2] = p_node2;
        pNode->child[3] = p_node3;
    
        pNode->child_num = 4;
    }

    一个辅助函数,给一个指定节点,和目标位置,返回所在的象限。
    象限划分:

    /* 一个矩形区域的象限划分:
        
        UL(1)   |    UR(0) 
        --------|----------- 
        LL(2)   |    LR(3) 
    
    */
            
    int QuadTree::GetIndex(PosInfo pos, QuadTreeNode *pNode)
    {
        int start_x = pNode->rect.lb_x;
        int start_y = pNode->rect.lb_y;
        int sub_width = (pNode->rect.rt_x - pNode->rect.lb_x) / 2;
        int sub_height = (pNode->rect.rt_y - pNode->rect.lb_y) / 2;
        int end_x = pNode->rect.rt_x;
        int end_y = pNode->rect.rt_y;
        
        start_x + sub_width, start_y + sub_height, end_x, end_y;
        //0象限
        if ((pos.latitude >= start_x + sub_width) 
        && (pos.latitude <= end_x) 
        && (pos.longitude >= start_y + sub_height) 
        && (pos.longitude <= end_y))
        {
            return UR;
        }
        //1象限
        else if ((pos.latitude >= start_x) 
        && (pos.latitude < start_x + sub_width) 
        && (pos.longitude >= start_y + sub_height) 
        && (pos.longitude <= end_y))
        {
            return UL;
        }
        //2象限
        else if ((pos.latitude >= start_x) 
        && (pos.latitude < start_x + sub_width) 
        && (pos.longitude >= start_y) 
        && (pos.longitude < start_y + sub_height))
        {
            return LL;
        }
        //3象限
        else if ((pos.latitude >= start_x + sub_width) 
        && (pos.latitude <= end_x) 
        && (pos.longitude >= start_y) 
        && (pos.longitude < start_y + sub_height))
        {
            return LR;
        }
        else
        {
            return INVALID;
        }
    }

    接下来是关键点,插入函数,当数据一组一组出现的时候,如何构建一棵四叉树:

    可以从根节点开始,如果该节点有孩子,则插入对应象限的孩子节点。

    否则的话,判断当前结点的位置信息容量和深度。

    如果位置信息还没满,深度在预先设定的范围内,则将位置信息插入当前结点。

    否则需要将当前结点分割,并且把位置信息分发到各个新的孩子节点,删除自身的位置信息。然后重新对当前结点做一次插入操作。

    void QuadTree::Insert(PosInfo pos, QuadTreeNode *p_node)
    {
        if (p_node == NULL)
        {
            return;
        }
    
        if (p_node->child_num != 0)
        {
            int index = GetIndex(pos, p_node);
            if (index >= UR && index <= LR)
            {
                Insert(pos, p_node->child[index]);
            }
        }
        else
        {
            if ((int)p_node->pos_array.size() >= m_maxobjects && p_node->depth < m_depth)
            {
                Split(p_node);
                for (std::vector<PosInfo>::iterator it = p_node->pos_array.begin(); it != p_node->pos_array.end(); ++it)
                {
                    Insert(*it, p_node);
                }
                p_node->pos_array.clear();
                Insert(pos, p_node);
            }
            else
            {
                p_node->pos_array.push_back(pos);
            }
        }
    }

    搜索附近的点:

    从根节点遍历到目标节点所在节点,遍历该节点所有的位置信息点。

    假如数量不够,则向上到父节点,搜寻兄弟节点,直到获得足够数量的位置信息点。

    void QuadTree::Search(int num, PosInfo pos_source, std::vector<PosInfo> &pos_list, QuadTreeNode *p_node)
    {
        if (p_node == NULL)
        {
            return;
        }
    
        if ((int)pos_list.size() >= num)
        {
            return;
        }
    
        if (p_node->child_num == 0)
        {
            for (std::vector<PosInfo>::iterator it = p_node->pos_array.begin(); it != p_node->pos_array.end(); ++it)
            {
                if ((int)pos_list.size() >= num)
                {
                    return;
                }
                pos_list.push_back(*it);
            }
            return;
        }
    
        //目标节点
        int index = GetIndex(pos_source, p_node);
        if (index >= UR && index <= LR)
        {
            if (p_node->child[index] != NULL)
            {
                Search(num, pos_source, pos_list, p_node->child[index]);
            }
        }
    
        //目标节点的兄弟节点
        for (int i = 0; i < CHILD_NUM; ++i)
        {
            if (index != i && p_node->child[i] != NULL)
            {
                Search(num, pos_source, pos_list, p_node->child[i]);
            }
        }
    }

    如果算法没错的||—_—||

    数据测试,随机1千万组数据,输入一个目标位置,搜索15个附近点的耗时是2毫秒。

    随机1亿组数据,耗时是37毫秒。(插入数据用了一个小时,打印了一个1G的TXT)。。。。

  • 相关阅读:
    小知识!
    命令级的python静态资源服务。
    自定义滚动条样式-transition无效
    css:a:visited限制
    react16 渲染流程
    virtual-dom
    用video标签流式加载
    golang 代码笔记
    position:fixed not work?
    go/node/python 多进程与多核cpu
  • 原文地址:https://www.cnblogs.com/yao2yaoblog/p/5475231.html
Copyright © 2020-2023  润新知