Алгоритм поиска синонимов

Я думаю, что пример будет намного лучше, чем описание loooong:)

предположим у нас есть массив массивов:

("Server1", "Server_1", "Main Server", "192.168.0.3")
("Server_1", "VIP Server", "Main Server")
("Server_2", "192.168.0.4")
("192.168.0.3", "192.168.0.5")
("Server_2", "Backup")

каждая строка содержит строки, являющиеся синонимами. И в результате обработки этого массива я хочу получить следующее:

("Server1", "Server_1", "Main Server", "192.168.0.3", "VIP Server", "192.168.0.5")
("Server_2", "192.168.0.4", "Backup")

поэтому я думаю, что мне нужен своего рода рекурсивный алгоритм. Язык программирования на самом деле не имеет значения - мне нужна только небольшая помощь с идеей в целом. Я собираюсь использовать php или python.

спасибо ты!

4 ответов


эта проблема может быть сведена к проблеме в теории графов, где вы найдете все группы узлов в графе.

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

чтобы увидеть, как исходный вопрос может быть сделан в вопрос на графиках: сделайте каждую запись (например, "Server1", "Server_1" и т. д.) узел на графике. Соединяйте узлы с ребрами, если и только если они являются синонимами. Матричная структура данных особенно подходит для отслеживания ребер при условии, что у вас достаточно памяти. В противном случае будет работать разреженная структура данных, такая как карта, тем более что количество синонимов, вероятно, будет ограничено.

  • Server1-это узел #0
  • Server_1-это узел #1
  • Server_2 является Узел #2

затем edge[0] [1] = edge[1] [0] = 1, указано, что существует ребро между узлами #0 и #1 ( Что означает, что они являются синонимами ). В то время как edge[0][2] = edge[2][0] = 0, указывая, что Server1 и Server_2 являются не синонимы.

Анализ Сложности

создание этой структуры данных довольно эффективно, потому что одного линейного прохода с поиском сопоставления строк с номерами узлов достаточно для создания ящика он. Если вы сохраните сопоставление строк с номерами узлов в словаре, это будет шаг O(N log n).

выполнение заливки потока O (n), вы посещаете каждый узел на графике только один раз. Итак, алгоритм во всем равен O (N log n).


ввести целочисленную маркировку, которая указывает группы синонимов. На старте один отмечает все слова с разными знаками от 1 до N.

затем поиск через вашу коллекцию, и если вы найдете два слова с индексами i и j являются синонимом, то замечание всех слов с пометкой i и j С меньшим количеством обоих. После N итерации вы получаете все группы синонимов.

это какое-то грязное и не слишком эффективное решение, я считаю можно получить больше производительности с помощью структур union-find.


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


PHP, кажется, работает (по крайней мере, с данными из данного примера):

$data = array(
    array("Server1", "Server_1", "Main Server", "192.168.0.3"),
    array("Server_1", "VIP Server", "Main Server"),
    array("Server_2", "192.168.0.4"),
    array("192.168.0.3", "192.168.0.5"),
    array("Server_2", "Backup"),
);

do {
    $foundSynonyms = false;
    foreach ( $data as $firstKey => $firstValue ) {
        foreach ( $data as $secondKey => $secondValue ) {
            if ( $firstKey === $secondKey ) {
                continue;
            }
            if ( array_intersect($firstValue, $secondValue) ) {
                $data[$firstKey] = array_unique(array_merge($firstValue, $secondValue));
                unset($data[$secondKey]);
                $foundSynonyms = true;
                break 2; // outer foreach
            }
        }
    }
} while ( $foundSynonyms );

print_r($data);

выход:

Array
(
    [0] => Array
        (
            [0] => Server1
            [1] => Server_1
            [2] => Main Server
            [3] => 192.168.0.3
            [4] => VIP Server
            [6] => 192.168.0.5
        )

    [2] => Array
        (
            [0] => Server_2
            [1] => 192.168.0.4
            [3] => Backup
        )

)

Это даст меньшую сложность, чем пример PHP (Python 3):

a = [set(("Server1", "Server_1", "Main Server", "192.168.0.3")),
    set(("Server_1", "VIP Server", "Main Server")),
    set(("Server_2", "192.168.0.4")),
    set(("192.168.0.3", "192.168.0.5")),
    set(("Server_2", "Backup"))]

b = {}
c = set()
for s in a:
    full_s = s.copy()
    for d in s:
        if b.get(d):
            full_s.update(b[d])
    for d in full_s:
        b[d] = full_s
    c.add(frozenset(full_s))

for k,v in b.items():
    fsv = frozenset(v)
    if fsv in c:
        print(list(fsv))
        c.remove(fsv)