大家好,
首先,我不打算创建社交网络, facebook 足够大! (漫画) 我选择这个问题作为例子,因为它完全符合我的目的。
想象一下,我在MySQL中有一个users
表和一个带有'friend requests'的user_connections
表。如果是这样,它将是这样的:
Users Table:
userid username
1 John
2 Amalia
3 Stewie
4 Stuart
5 Ron
6 Harry
7 Joseph
8 Tiago
9 Anselmo
10 Maria
User Connections Table:
userid_request userid_accepted
2 3
7 2
3 4
7 8
5 6
4 5
8 9
4 7
9 10
6 1
10 7
1 2
现在我想在朋友之间找到圈子和创建一个结构数组并将该圈子放在数据库上(所有阵列都不能包含相同的朋友另一个已经)。
Return Example:
// First Circle of Friends
Circleid => 1
CircleStructure => Array(
1 => 2,
2 => 3,
3 => 4,
4 => 5,
5 => 6,
6 => 1,
)
// Second Circle of Friends
Circleid => 2
CircleStructure => Array(
7 => 8,
8 => 9,
9 => 10,
10 => 7,
)
我正在考虑使用算法来做到这一点,但我认为这需要花费大量的处理时间,因为它会随机搜索数据库,直到它“关闭”一个圆圈。
PS:圆圈的最小结构长度为3个连接,限制为100(因此守护程序不会搜索整个数据库)
修改
我想到这样的事情:
function browse_user($userget='random',$users_history=array()){
$user = user::get($userget);
$users_history[] = $user['userid'];
$connections = user::connection::getByUser($user['userid']);
foreach($connections as $connection){
$userid = ($connection['userid_request']!=$user['userid']) ? $connection['userid_request'] : $connection['userid_accepted'];
// Start the circle array
if(in_array($userid,$users_history)) return array($user['userid'] => $userid);
$res = browse_user($userid, $users_history);
if($res!==false){
// Continue the circle array
return $res + array($user['userid'] => $userid);
}
}
return false;
}
while(true){
$res = browse_user();
// Yuppy, friend circle found!
if($res!==false){
user::circle::create($res);
}
// Start from scratch again!
}
此功能的问题在于它可以搜索整个数据库而无需找到最大的圆圈或最佳匹配。
答案 0 :(得分:11)
对大型数据集执行此类操作对于单个批处理来说几乎总是(太)大工作。处理大量数据时,应该连续构建索引。每次添加“用户”或与另一个“用户”成为“朋友”时进行循环测试,并将结果圆存储在索引表中。然后当新的“用户”注册或与另一个“用户”成为“朋友”时,您应该使用您的索引数据库根据旧的圈子查找新的圈子。
修改强>
我对这个问题非常热心,所以我做了一个关于如何解决这个问题的概念验证类boisvert的想法。我将代码放在GitHub上:https://github.com/alfreddatakillen/Six-Degrees-of-Separation (在这里发布它会有点麻烦。)
它看起来效果很好。但是,我没有用大量数据对其进行测试。我很确定你必须优化它,因为它基本上是一个暴力攻击存储每一步......(如果你做优化或使用代码,我会很高兴,如果你推动变化到GitHub - 我可能想在将来的项目中使用它。)
: - )
答案 1 :(得分:9)
Alfred Godoy的答案非常有用,因为处理应该逐步完成。我会尝试充实一些具体的细节。
我不是说“人”是“朋友”,而是谈论“边缘”连接起来的“节点”。周期从3增加到4(或n到n + 1)个节点并不是真的。几个边缘,不形成一个循环,可以通过一个新的边缘关闭,形成一个新的循环。
因此,相反(或同样)作为循环列表,我们应该维护一个边链列表。例如假设此连接表:
userid_request userid_accepted
2 7
3 7
3 8
然后Chain表应该包含:
chainid start end length
1 2 3 2
1 2 8 3
2 7 8 2
此结构允许您记录和检索任意长度的链,并给定链的两端,使用id和length跟随它。这需要空间......但这是在图中为您搜索周期的内存成本。它可以简化,具体取决于你想要做什么;详细说明。
顺便说一下,我假设图表是无向的 - 换句话说,从一个节点到另一个节点的边缘都是双向的。在连接中,具有最低id的节点将始终是“请求”,并且在“接受”端具有最高id。
添加新节点时,您希望维护链表并查看新节点是否关闭链循环。它涉及几个问题。我给你一个。
假设在Connection表中添加了一个新条目:
userid_request userid_accepted
@new_request @new_accepted
您可以使用查询来选择任何此类周期。您已经了解了新链接及其两个节点,因此您只需要查找关闭它的链:
select chainid, length
from chain
where (start = @new_request and end = @new_accepted)
or (end = @new_request and start = @new_accepted)
(请注意,由于图表未定向,因此您必须查找两个周期配置。)
每次添加边时,要维护链表:
然后当删除节点时,你应该取出相应的链和周期 - 链表将足够大。
答案 2 :(得分:1)
您是否尝试执行Six Degrees of Seperation
之类的操作这首先看起来像NP难题,因为为了计算每个圆圈,你必须遍历图中的每个节点,找到那里所有可能的圆圈。我相信没有简单的方法可以做到这一点,而且很可能没有办法实现这一点。
所以你应该在作业中执行这样的计算,缓存结果以提高效率。
在我的脑海中,如果我们将此问题视为图形,每个用户作为节点,每个关系作为权重为1的边缘,我们可以使用广度优先搜索从用户1查找所有路径最大在每个节点遍历(最多总重6)上,我们搜索起始节点和当前节点是否共享边缘。如果是这样,我们关闭圆圈。它将以2的圆圈开始并向前展开。如果我们达到权重6并且最终节点不与起始节点共享边缘,则丢弃该圆圈。
可能在一个真实的情况下加快速度,您可以尝试为每个用户计算最小总重量(比如3)的圆圈,然后尝试加入圆圈以达到最高6。
答案 3 :(得分:0)
只是创建圆圈的想法 - 每次用户尝试查看圆圈时都会加载此功能:
尝试这种方式很奇怪,我知道
答案 4 :(得分:0)
我认为对于这种类型的JOB,您应该在NOSQL数据库上进行转发,这些数据库很擅长您正在寻找的可能是像Neo4j这样的图形数据库。