Подтвердить что ты не робот

Вызов, как реализовать алгоритм для шести степеней разделения?

UserA-UserB-UserC-UserD-UserF

Пользователи, связанные друг с другом, знают друг друга.

И мне нужен алгоритм для этих двух задач:

  • Вычислить путь от UserX к UserY
  • Для UserX, вычислите всех пользователей, которые находятся на расстоянии не более 3 шагов.

Существует ли эффективное решение?

ИЗМЕНИТЬ

Моя цель состоит не в том, чтобы доказать это правильно или неправильно, но при необходимости вычислить результат в реальном времени.

Плюс, я считаю, что самым выразительным способом является код, даже псевдо.

ИЗМЕНИТЬ СНОВА

Я решил, что такая работа должна выполняться внутри базы данных, поэтому это должно быть решение sql!

4b9b3361

Ответ 1

Представляем этот список пользователей по графику

  • Каждый пользователь имеет node
  • Между любыми двумя пользователями, которые знают друг друга, есть преимущество.
  • Вычислить путь от UserX к UserY
  • Для UserX, вычислите всех пользователей, которые находятся на расстоянии не более 3 шагов.

Эти вопросы так тесно связаны, что один и тот же алгоритм фактически решает их оба. Вы можете назвать это "алгоритмом Дейкстры со всеми ребрами, имеющими вес 1", или "поиск по ширине".

По существу, начиная с первого node, посетите всех своих родственников; затем отметьте их всеми, как посетили, запишите кратчайший путь к каждому (кратчайший путь к ним + край, который вы только что прошли) и повторите для каждого из них. Остановитесь после того, как вы достигли места назначения для задачи №1, остановитесь после кратчайшего пути > 3 для проблемы № 2.

Это будет работать в O (n) времени. Нет, нет более быстрого способа.

Самый быстрый алгоритм O (n) для шести степеней разделения, вероятно, будет найти наборы всех пользователей в 1-шаге от UserX и UserY и найти пересечение этих двух наборов. Если нет, добавьте пользователей из 2-х шагов из UserX и пересекайтесь; затем добавьте пользователей в 2 шага из UserY и пересекайтесь; и т.д. до 3.

Если каждый человек имеет в среднем 100 друзей, для этого может потребоваться примерно до 2,020,200 пользователей, в отличие от 1,010 миллиардов для алгоритма Дейкстры. На практике эти цифры были бы намного меньше, так как часто два из ваших друзей также дружат друг с другом.

Это единственный метод решения шести степеней разделения (из тех, что указаны до сих пор) , который будет работать на практике.

Ответ 2

Графические алгоритмы могут помочь вам здесь. Узнавать о них тоже весело!

  • Графическая связь для подключения.
  • Dijkstra (A *) для путей между пользователями
  • Простая DFS для нахождения всех узлов N пользователей от вашего пользователя.

Dijkstra или подобное должно использоваться, если вы хотите найти кратчайший путь между двумя пользователями. Между ними может быть несколько путей, и Дейкстра позаботится о том, чтобы узнать, когда он нашел короткий путь, чем другой, который был найден раньше.

Чтобы найти кратчайшие пути между всеми пользователями, вам нужно будет использовать что-то вроде 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] );

Ответ 3

У меня есть предложение, которое сильно отличается от тех, которые у вас уже есть. Если вам нужно придерживаться базы данных SQL и вы не знаете java, это предложение не будет иметь большого смысла.

Проблема связана с проблемой графа, поэтому я бы предположил, что при использовании базы данных SQL для хранения графика будет работать другой подход - использовать решение, специально предназначенное для задач с графами.

Проект Neo4j предоставляет базу данных на основе диска, вместе с ней будет работать множество алгоритмов графа. Цитата:

Neo4j - это база данных графа. Это встроенный, основанный на диске, полностью транзакционный механизм сохранения Java который хранит данные, структурированные в графах а не в таблицах. Граф (математический жаргон для сети) гибкая структура данных, которая позволяет более гибкий и быстрый стиль развитие.

Соответствующий пример использования Neo4j в их вики демонстрирует градуированное разделение веб-приложения с использованием данных IMDB. Пример иллюстрирует вычисления кратчайшего пути между любым актером и Кевином Бэконом.

Мне нравится этот пример, поскольку он много говорит о моделировании домена, который будет представлять ваш граф. Моделирование вашего домена гарантирует, что вы подумали о таких вещах, как:

  • Directed vs Undirected
  • Типы/отношения края
  • Атрибуты, такие как вес ребер

Как уже упоминалось в других сообщениях, существует ряд алгоритмов для вычисления кратчайших путей, таких как Dijkstra, Floyd Warshall или BFS. Все это было реализовано в Neo4j, и некоторые примеры представлены здесь.

Ответ 4

Предполагая исходные данные в таблице: Соединения: (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

EDIT. В приведенном выше решении я изначально забыл исключить пользователей уже в таблице temp #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

Ответ 5

Следующие сценарии написаны в sybase sql. Возможно, вам придется пройти незначительные изменения в соответствии с вашим сервером db.

Задача 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

Ответ 6

Я посмотрел на это некоторое время назад и не смог найти эффективное решение для веб-приложения.

Я закончил с 5 уровнями, а не с шестью

См. здесь сообщение google group, есть SQL и С# решение.

Примечание:, что вы должны google для "алгоритма Дейкстры", поскольку он известен как хороший алгоритм для поиска кратчайшего пути.

Изменить: Попробуйте ссылка

BTW Самый быстрый метод CLR.

Ответ 7

Первый вопрос может быть решен с использованием алгоритма dijkstra. Второй - при использовании алгоритма DFS. Об этом уже говорили другие ребята, просто хотел отметить, что наиболее эффективное решение для обеих проблем недоступно в одном алгоритме.

псевдокод можно найти по адресу:

[Википедия] [1]

для dijkstra и один в python для DFS:

http://en.wikipedia.org/wiki/Depth-first_search

Ответ 8

Для задачи 2 вы не будете делать лучше, чем поиск по ширине, за исключением, может быть, кэширования.

Для задачи 1 примените свое решение для задачи 2. Найдите всех пользователей не более, чем на 3 удаленных от пользователя X. Конечно, если пользователь Y находится в этом наборе, все готово. Если нет, выполните поиск по ширине, начинающийся с пользователя Y, и остановитесь, как только достигнете любого пользователя, которого вы уже знаете, можно добраться из X.

(Если вы кешируете небольшую информацию о том, как вы достигли каждого пользователя во время задачи 2, тогда будет легко восстановить точный путь, когда вы найдете ссылку в задаче 1.)

Ответ 9

(Этот ответ эквивалентен Djikstra's. Это в основном деталь реализации.)

Чтобы ответить на # 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 для добавления:

Вы можете оптимизировать это, только делая умножение для записей, которые ранее были ложными, а также понимая, что матрица симметрична. Это остается как упражнение для читателя.

Ответ 10

Google, и вы найдете много.

Я сомневаюсь, что вы можете найти псевдокод (по крайней мере, мне еще нужно). Вот некоторые из интересных замечаний:

"Шесть степеней разделения" , объясненных в новом алгоритме компьютера
Ученый-компьютер CU помогает объяснить, как работает "шесть степеней разделения" SO - Как я могу доказать концепцию "Six Degrees of Separation" программно?

Ответ 11

Я отвечаю только на SQL-решение. Это дает все пути в 3 шага, хотя это может быть не "эффективно" для больших наборов данных. Таблицы "KNOW", "KNOW_1" и т.д. Идентичны и имеют два поля P1 и P2. Он имеет запись только в том случае, если 1) P1 знает P2 или 2) P2 знает P1. Данные в P1 и P2 могут быть произвольными строками, соответствующими каждому человеку.

В этом SQL-запросе Acccess должны быть указаны все пути, в которых известно, что b знает c знает d без циклов (например, знает, знает ли b знает c знает a). Вам по-прежнему необходимо устранить дубликаты (abcd = dcba), но они должны быть в состоянии сделать это легко на втором шаге. Циклы устраняются путем предотвращения повторений предыдущих людей в инструкциях Where.

SELECT Know.P1, Know.P2, Know_1.P2, Know_2.P2

FROM (Знать ВНУТРЕННЕЙ ВЕЩЬ Знать 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) < > [Знать]. [P1]) AND ((Know_2.P2) < > [Знать]. [P1] И (Know_2.P2) < > [Знать ]. [P2]))

ЗАКАЗ BY Know.P1, Know.P2, Know_1.P2, Know_2.P2;

Не так элегантно, как предыдущие решения, но, похоже, работает нормально. У нас был некоторый опыт работы с аналогичной работой с программированием ограничения и выяснилось, что процесс SQL быстрее для некоторых проблем.