messenger支持查找附近的人功能吗_现在很多的APP都有“附近的人”功能,这是哪个知识实现的呢!...

前言:粗略的思考一下,用户在登录的时候会将自己的位置信息告诉服务器,服务器会记录一份用户的位置信息列表。假设服务器里只有10个人,那么要找附近的人就很简单,只需写一个算距离的函数,然后依次遍历长度是10的位置信息列表,距离从近到远排序,返回排序后的列表即可。那么如果服务器里有1千万人呢,或者几亿人呢,比如微信。这时再从头到尾遍历(复杂度O(n))就不适合了。通常情况下会使用“四叉树”来构建这些大量的位置信息。

一、四叉树

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

二、地理位置和四叉树

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

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

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

三、查找附近的点

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

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

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

四、C++描述算法

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

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

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

struct QuadTreeNode

{

QuadTreeNode(){}

Rect rect; //节点代表的矩形区域 std::vector 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;

}

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

象限划分:

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::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 &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::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)。

为了答谢大家关注和支持,这次给大家准备了限时领取福利:阿里面试题、百度面试题、滴滴面试题、华为面试题、京东面试题、美团面试题、腾讯面试题、头条面试题、中兴面试题。

还等什么小编推荐自己的linuxC/C++语言交流群:【前100名进群领取,额外赠送一份价值199的C/C++、linux资料包含(视频教程、电子书、实战项目及代码),下面部分展示。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值