用户A-用户B-用户C-UserD-UserF
通过' - '连接的用户互相认识。
我需要一个算法来完成这两项任务:
是否有有效的解决方案?
修改
我的目的不是证明它是对还是错,而是在必要时实时计算结果。
另外,我认为最具表现力的方式是代码,甚至伪代码。
再次编辑
我已经决定这种工作必须在数据库内完成,所以它必须是一个sql解决方案!
答案 0 :(得分:16)
通过图表
表示此用户列表
- 计算从UserX到UserY的路径
- 对于UserX,计算距离不超过3步的所有用户。
醇>
这些问题密切相关,相同的算法实际上解决了这两个问题。您可以将其命名为“Dijkstra算法,所有边缘都具有权重1,”或“广度优先搜索。”
基本上,从第一个节点开始,访问其所有亲戚;然后将它们全部标记为已访问,记录每个最短路径(到达它们的最短路径+刚刚遍历的边缘),并为每个路径重复。到达问题#1的目的地后停止,在最短路径后停止>问题#2为3。
用于六度分离的最快O(n)算法可能会找到距离 UserX 和 UserY一步的所有用户的集合,并找到这两组的交集。如果没有,则从 UserX 添加用户两步并交叉;然后从 UserY 添加用户2步并交叉;最多3个。
如果每个人平均有100个朋友,这可能需要查看大约 2,020,200个用户,而不是Dijkstra算法的 1,010亿。在实践中,这些数字会小得多,因为你的两个朋友通常也是彼此的朋友。
这是解决六度分离的唯一方法 (目前为止提到的那些) 将在实践中发挥作用。 < / p>
答案 1 :(得分:8)
图形算法可以为您提供帮助。了解它们也很有趣!
要查找所有用户之间的最短路径,您必须使用Floyd-Warshall之类的内容。这是动态编程的一个很好的例子,实现起来非常简单。来自维基百科的伪代码是:
1 /* Assume a function edgeCost(i,j) which returns the cost of the edge from i to j
2 (infinity if there is none).
3 Also assume that n is the number of vertices and edgeCost(i,i) = 0
4 */
5
6 int path[][];
7 /* A 2-dimensional matrix. At each step in the algorithm, path[i][j] is the shortest path
8 from i to j using intermediate vertices (1..k−1). Each path[i][j] is initialized to
9 edgeCost(i,j) or infinity if there is no edge between i and j.
10 */
11
12 procedure FloydWarshall ()
13 for k := 1 to n
14 for i := 1 to n
15 for j := 1 to n
16 path[i][j] = min ( path[i][j], path[i][k]+path[k][j] );
答案 2 :(得分:6)
我的建议与您已有的建议完全不同。如果你必须坚持使用SQL数据库并且你不知道任何java,那么这个建议将没什么用处。
你的问题特别是一个图形问题,所以我建议虽然使用SQL数据库来存储图形是可行的,但另一种方法是使用专门用于图形问题的解决方案。
Neo4j项目提供了一个基于磁盘的图形数据库,它将共同提供许多图形算法。引用:
Neo4j是一个图形数据库。它是一个 嵌入式,基于磁盘,完全 事务性Java持久性引擎 存储以图形结构化的数据 而不是在表格中。图表 (网络的数学术语)是 允许的灵活数据结构 一种更敏捷,更快速的风格 发展。
在他们的wiki上使用Neo4j的适当示例演示了degrees-of-separation web application使用IMDB数据。这个例子说明了任何演员和凯文·培根之间的最短路径计算。
我喜欢这个例子,因为它谈论了很多关于图形将代表的域的建模。对您的域建模可确保您考虑过以下内容:
正如其他帖子中所提到的,有许多用于计算最短路径的算法,例如Dijkstra,Floyd Warshall或BFS。所有这些都已在Neo4j中实现,并提供了一些示例here。
答案 3 :(得分:6)
假设源数据在表中:Connections:(PersonID,KnowsPersonID)
1)这必须采用广度优先的方法。由于问题的指数性,你的良好表现潜力是有限的(尽管这种指数性质是理论上你只需要 6 度:D)的原因。 确保限制搜索深度。无论您选择哪种SQL,您最好使用其迭代扩展而不是基于纯集的解决方案。
以下是使用Microsoft的T-SQL的基本方法:
CREATE PROCEDURE FindPath (@UserX int, @UserY int)
CREATE TABLE #SixDegrees(
ConnectedUser int,
Depth int,
Path varchar(100),
CONSTRAINT PK_SixDegrees PRIMARY KEY CLUSTERED (
ConnectedUser
)
)
DECLARE @Depth int,
@PathFound varchar(100)
SET @Depth = 0
INSERT INTO #SixDegrees (@UserX, 0, CAST(@UserX as varchar))
/*Next line just in case X & Y are the same*/
SET @PathFound = (SELECT Path
FROM #SixDegrees
WHERE ConnectedUser = @UserY)
WHILE @Depth < 6 AND @PathFound IS NULL
BEGIN
SET @Depth = @Depth + 1
INSERT INTO #SixDegrees
SELECT k.KnowsPersonID, @Depth, (SELECT Path
FROM #SixDegrees
WHERE ConnectedUser = k.Link) + ',' + CAST(k.KnowsPersonID AS varchar)
FROM (
SELECT MIN(ConnectedUser) Link, KnowsPersonID
FROM #SixDegrees
JOIN Connections ON
PersonID = ConnectedUser
WHERE Depth = @Depth
/*EDIT: Added the following*/
AND KnowsPersonID NOT IN (
SELECT ConnectedUser
FROM #SixDegrees
)
GROUP BY KnowsPersonID
) k
SET @PathFound = (SELECT Path
FROM #SixDegrees
WHERE ConnectedUser = @UserY)
END
IF @Path IS NULL
PRINT 'No path found'
ELSE
PRINT @Path
GO
编辑:在上面的解决方案中,我最初忘记排除已经在#SixDegrees临时表中的用户。
2)对上面的一点调整总是循环到3的深度会让你的#SixDegrees包含你感兴趣的所有用户。
但是,以下基于纯集的解决方案应该更有效:
SELECT DISTINCT KnowsPersonID
FROM Connections
WHERE PersonID IN (
SELECT DISTINCT KnowsPersonID
FROM Connections
WHERE PersonID IN (
SELECT KnowsPersonID
FROM Connections
WHERE PersonID = @UserX
) l1
) l2
答案 4 :(得分:5)
以下脚本是用sybase sql编写的。您可能需要根据数据库服务器进行微小的修改。
问题1。
create table #connections (
my_user varchar(10) not null ,
knows varchar(10) not null ,
CONSTRAINT connection_pk PRIMARY KEY CLUSTERED ( my_user, knows)
)
create table #traversed (id varchar(10) primary key)
insert into #connections VALUES ('UserA','UserB')
insert into #connections VALUES ('UserB','UserA')
insert into #connections VALUES ('UserB','UserC')
insert into #connections VALUES ('UserC','UserB')
insert into #connections VALUES ('UserC','UserD')
insert into #connections VALUES ('UserD','UserC')
insert into #connections VALUES ('UserD','UserF')
insert into #connections VALUES ('UserF','UserD')
DECLARE @str_sql varchar(200)
DECLARE @str_order varchar(60)
declare @start varchar(10)
set @start = ('UserD')
declare @end varchar(10)
set @end = ('UserA')
if (@start >= @end)
set @str_order = " order by id desc"
else
set @str_order = " order by id asc"
INSERT INTO #traversed VALUES (@start)
WHILE (select count(*) from #traversed where id = @end) = 0
BEGIN
INSERT INTO #traversed (id)
SELECT DISTINCT knows
FROM #connections e JOIN #traversed p ON p.id = e.my_user
WHERE e.knows NOT IN (SELECT id FROM #traversed)
AND e.knows between (select case when @start < @end then @start else @end end)
and (select case when @start < @end then @end else @start end)
END
set @str_sql = "SELECT #traversed.id FROM #traversed" + @str_order
exec (@str_sql)
问题2。
create table #connections (
my_user varchar(10) not null ,
knows varchar(10) not null ,
CONSTRAINT connection_pk PRIMARY KEY CLUSTERED ( my_user, knows)
)
create table #traversed (id varchar(10) primary key)
insert into #connections VALUES ('UserA','UserB')
insert into #connections VALUES ('UserB','UserA')
insert into #connections VALUES ('UserB','UserC')
insert into #connections VALUES ('UserC','UserB')
insert into #connections VALUES ('UserC','UserD')
insert into #connections VALUES ('UserD','UserC')
insert into #connections VALUES ('UserD','UserF')
insert into #connections VALUES ('UserF','UserD')
declare @start varchar(10)
set @start = ('UserB')
declare @higher_counter int
declare @lower_counter int
set @higher_counter = 0
set @lower_counter = 0
INSERT INTO #traversed VALUES (@start)
WHILE (@higher_counter < 3)
BEGIN
INSERT INTO #traversed (id)
SELECT DISTINCT knows
FROM #connections e JOIN #traversed p ON p.id = e.my_user
WHERE e.knows NOT IN (SELECT id FROM #traversed)
AND e.knows > @start
set @higher_counter = @higher_counter +1
END
WHILE (@lower_counter < 3)
BEGIN
INSERT INTO #traversed (id)
SELECT DISTINCT knows
FROM #connections e JOIN #traversed p ON p.id = e.my_user
WHERE e.knows NOT IN (SELECT id FROM #traversed)
AND e.knows < @start
set @lower_counter = @lower_counter +1
END
SELECT #traversed.id FROM #traversed
答案 5 :(得分:2)
我刚才看过这个问题,无法为网络应用程序提供有效的解决方案。
我最终得到了5个级别而不是6个级别
在这里查看我的google group post有一个SQL和一个C#解决方案。
注意:您应该谷歌搜索“Dijkstra算法”,因为它已知是一种很好的算法来找到最短路径。
修改:试试这个link
BTW CLR方法执行得最快。
答案 6 :(得分:2)
第一个问题可以用dijkstra算法解决。 第二个使用DFS算法。 其他人已经说过,只是想指出,两种问题的最有效解决方案在一种算法中都不可用。
伪代码可以在:
找到[维基百科] [1]
代表dijkstra,另一个代表python代表DFS:
答案 7 :(得分:2)
对于任务2,除了广度优先搜索之外,你不会做得更好。除了可能通过缓存。
对于任务1,将应用程序应用于任务2.查找距离用户X不超过3跳的所有用户。当然,如果用户Y在该集合中,那么您就完成了。如果没有,请从用户Y开始进行广度优先搜索,并在您到达任何已知的用户时立即停止。
(如果你在任务2中缓存了一些关于你如何到达每个用户的信息,那么当你在任务1中找到一个链接时,很容易重建确切的路径。)
答案 8 :(得分:2)
(这个答案相当于Djikstra的。它基本上是一个实现细节。)
要回答#2,您可以使用布尔矩阵乘法来确定程度为P
的连通性。
假设你有一个布尔矩阵M
,其中:
M(A, B)= A is directly connected to B
然后
(M(A, B))^P= A is connected to B within P links.
矩阵乘法应使用AND
进行乘法,使用OR
进行加法:
您可以通过仅对先前为假的条目进行乘法以及意识到矩阵是对称的来对此进行大量优化。这是留给读者的练习。
答案 9 :(得分:2)
实际上,使用OQGraph使用MariaDB可以做到这一点。
假设数据包含在两个表中:
CREATE TABLE `entity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` enum('ACTOR','MOVIE','TV MOVIE','TV MINI','TV SERIES','VIDEO MOVIE','VIDEO GAME','VOICE','ARCHIVE') NOT NULL,
`name` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `type` (`type`,`name`) USING BTREE
) ENGINE=InnoDB;
CREATE TABLE `link` (
`rel_id` int(11) NOT NULL AUTO_INCREMENT,
`link_from` int(11) NOT NULL,
`link_to` int(11) NOT NULL,
PRIMARY KEY (`rel_id`),
KEY `link_from` (`link_from`,`link_to`),
KEY `link_to` (`link_to`)
) ENGINE=InnoDB;
然后可以将OQGraph虚拟表声明为:
CREATE TABLE movie_graph (
latch SMALLINT UNSIGNED NULL,
origid BIGINT UNSIGNED NULL,
destid BIGINT UNSIGNED NULL,
weight DOUBLE NULL,
seq BIGINT UNSIGNED NULL,
linkid BIGINT UNSIGNED NULL,
KEY (latch, origid, destid) USING HASH,
KEY (latch, destid, origid) USING HASH
) ENGINE=OQGRAPH
data_table='link' origid='link_from' destid='link_to';
然后可以查询数据:
MariaDB [imdb]> SELECT
-> GROUP_CONCAT(name ORDER BY seq SEPARATOR ' -> ') AS path
-> FROM imdb_graph JOIN entity ON (id=linkid)
-> WHERE latch=1
-> AND origid=(SELECT a.id FROM entity a
-> WHERE name='Kevin Bacon')
-> AND destid=(SELECT b.id FROM entity b
WHERE name='N!xau')\G
*************************** 1. row ***************************
path: Kevin Bacon -> The 45th Annual Golden Globe Awards (1988) -> Richard Attenborough -> In Darkest Hollywood: Cinema and Apartheid (1993) -> N!xau
1 row in set (10 min 6.55 sec)
大约370万个节点和3000万个边的图形。表大约是3.5GB,笔记本电脑硬盘上为512MB缓冲池配置了InnoDB。大约有1600万次辅助密钥读取。冷,没有数据预加载到缓冲池中。 2010 MacBook Pro。
当然,如果表可以保存在缓冲池中,则速度要快得多。
此示例来自:https://www.slideshare.net/AntonyTCurtis/oqgraph-scale2013-17332168/21
答案 10 :(得分:1)
Google它,你会发现很多。
我怀疑你能找到一个伪代码(至少我还没有)。以下是一些有趣的读物:
"Six Degrees of Separation" Explained In New Computer Algorithm
CU computer scientist helps explain how 'six degrees of separation' works
SO - How can I prove the “Six Degrees of Separation” concept programmatically?
答案 11 :(得分:1)
我只响应SQL解决方案。这为所有路径提供了3步之遥,尽管对于大型数据集可能不是“高效”。表“KNOW”,“KNOW_1”等都是相同的,并且具有两个字段P1和P2。只有当1)P1知道P2或2)P2知道P1时才有条目。 P1和P2中的数据可以是对应于每个人的任意字符串。
这个Acccess SQL查询应该产生知道b知道c知道没有周期的所有路径(例如,知道b知道c知道a)。您仍然需要消除重复项(abcd = dcba),但应该能够在第二步中轻松完成。通过防止Where语句中的先前人员的重复来消除循环。
SELECT Know.P1,Know.P2,Know_1.P2,Know_2.P2
FROM(了解INNER JOIN了解AS Know_1 ON Know.P2 = Know_1.P1)
INNER JOIN了解AS Know_2 ON Know_1.P2 = Know_2.P1
WHERE(((Know_1.P2)&lt;&gt; [Know]。[P1])AND((Know_2.P2)&lt;&gt; [Know]。[P1] And(Know_2.P2)&lt;&gt; ; [专有] [P2]))
通过Know.P1,Know.P2,Know_1.P2,Know_2.P2订购;
不像以前的解决方案那么优雅,但似乎工作正常。我们在使用约束编程进行类似工作方面有一些经验,并发现SQL进程对于某些问题更快。