Поиск пути при принудительном использовании уникальных атрибутов узла, какой алгоритм следует использовать?

Обновление от 28 декабря 2011 г. Вот запись в блоге с менее расплывчатым описанием проблемы, которую я пытался решить, моей работы над ней и моего текущего решения: Наблюдение за игрой каждой команды MLB


Я пытаюсь решить своего рода странную задачу по поиску пути. У меня есть ациклический направленный граф, и каждое ребро имеет значение расстояния. И я хочу найти кратчайший путь. Просто, верно? Ну, есть пара причин, по которым я не могу просто использовать Дейкстру или А*.

  1. Меня совершенно не волнует ни начальный узел моего пути, ни конечный узел. Мне просто нужен путь, включающий ровно 10 узлов. Но:
  2. У каждого узла есть атрибут, скажем, цвет. Каждый узел имеет один из 20 возможных цветов.
  3. Путь, который я пытаюсь найти, является кратчайшим путем ровно с 10 узлами, где каждый узел имеет свой цвет. Я не хочу, чтобы ни один из узлов на моем пути имел тот же цвет, что и любой другой узел.
  4. Было бы неплохо иметь возможность заставить мой путь иметь одно значение для одного из атрибутов (например, по крайней мере один узел должен быть синим), но это на самом деле не обязательно.

Это упрощенный пример. Мой полный набор данных на самом деле имеет три разных атрибута для каждого узла, которые должны быть уникальными, и у меня есть более 2000 узлов, каждый из которых имеет в среднем 35 исходящих ребер. Поскольку получение идеального «кратчайшего пути» может быть экспоненциальным или факториальным временем, исчерпывающий поиск на самом деле не вариант. Что мне действительно нужно, так это некое приближение "хорошего пути", отвечающее критерию №3.

Может ли кто-нибудь указать мне алгоритм, который я мог бы использовать (даже модифицированный)?


Немного статистики по моему полному набору данных:

  • Всего узлов: 2430
  • Всего ребер: 86524
  • Узлы без входящих ребер: 19
  • Узлы без исходящих ребер: 32
  • Наибольшее количество исходящих ребер: 42
  • Среднее количество ребер на узел: 35,6 (в каждом направлении)
  • Из-за характера данных я знаю, что график ациклический
  • И в полном наборе данных я ищу путь длиной 15, а не 10

person Plutor    schedule 09.12.2011    source источник
comment
Кратчайший путь ровно с 10 узлами? Я немного запутался, можете ли вы прояснить эту часть?   -  person biziclop    schedule 10.12.2011
comment
Ваша задача не имеет ничего общего с краткостью (длиной), почему вы несколько раз упомянули кратчайший путь?   -  person Karoly Horvath    schedule 10.12.2011
comment
Извините, я не упомянул, что все ребра также имеют значения, так как это казалось нормальной частью поиска кратчайших путей. Я добавлю это сейчас.   -  person Plutor    schedule 10.12.2011
comment
При использовании подхода «сначала в глубину» временная сложность равна O(b^d), где d — глубина — равна 10, а b — ширина. Я не могу вывести широту из того, что вы здесь написали. Глубина равна 10 от указанного вами количества узлов.   -  person Richard Povinelli    schedule 12.12.2011
comment
В моем полном наборе данных глубина равна 15 (я упростил вопрос) и (даже будучи относительно умным в отношении обрезки ветвей) ширина в среднем составляет 35. И поскольку я ищу какой-то путь, не обязательно начиная с определенного узла, исчерпывающий поиск будет включать поиск в каждом из ~ 2000 узлов. Итак, около 2000 * 35 ^ 15 возможных путей.   -  person Plutor    schedule 12.12.2011
comment
Так что исчерпывающий поиск практически невозможен :-)   -  person Richard Povinelli    schedule 12.12.2011
comment
Не могли бы вы дать оценку количества входящих ребер на узел? Не могли бы вы дать оценку количества корней и количества конечных узлов? (Я собираюсь создать скрипт для создания поддельных данных для этой проблемы)   -  person wildplasser    schedule 14.12.2011
comment
@wildplasser: В пост добавлена ​​статистика.   -  person Plutor    schedule 14.12.2011
comment
Спасибо. Хм... 19 корней и только 32 терминала Типичная длина пути должна быть около 25? ... Я опубликую здесь скрипт для создания поддельных данных. BRB Кстати: что это? белково-сворачивающий?   -  person wildplasser    schedule 14.12.2011
comment
Нет, это нечто гораздо более прозаичное. Скажем так, я пытаюсь запланировать поездку. Узлы - это события, а цвета - это конкретные места (и я хочу посетить 15 событий в 15 разных городах).   -  person Plutor    schedule 14.12.2011
comment
Ах, коммивояжер с ограниченной долговременной памятью...   -  person wildplasser    schedule 14.12.2011
comment
Это не может быть неразрешимой проблемой Конечно, может — учитывая то, что вы нам рассказали, есть простое, сохраняющее цель сокращение от неметрической TSP, которая полностью неприступный. Если бы вы могли быть более конкретными, это могло бы много помочь.   -  person Per    schedule 15.12.2011
comment
Но если это проблема типа TSP, то почему это DAG? Для меня это не имеет смысла. (ну: это может быть прогулка на лодке вниз по течению...)   -  person wildplasser    schedule 18.12.2011
comment
Это DAG, потому что каждое событие происходит в одну конкретную дату. Если вы посещаете какое-либо мероприятие, вы можете посетить только то, которое идет после в хронологическом порядке. Так что это своего рода путешествие на лодке вниз по течению.   -  person Plutor    schedule 19.12.2011


Ответы (6)


Это тот случай, когда вопрос на самом деле содержит большую часть ответа.

Выполните поиск в ширину, начиная со всех корневых узлов. Когда количество параллельно просматриваемых путей превышает некоторый предел, самые длинные пути отбрасываются. Длину пути можно взвешивать: последние ребра могут иметь вес 10, ребра, пройденные 9 прыжков назад - вес 1. Также можно присвоить меньший вес всем путям, имеющим предпочтительный атрибут, или путям, проходящим через слабосвязанные узлы. Сохраните последние 10 узлов в пути к хеш-таблице, чтобы избежать дублирования. И сохраните где-нибудь минимальную сумму последних 9 длин ребер вместе с кратчайшим путем.

person Evgeny Kluev    schedule 14.12.2011
comment
Это интересная стратегия. Я должен попробовать. Есть ли для него название? Это ветвь и граница? - person Plutor; 14.12.2011
comment
Я не знаю, классический это алгоритм или нет. Никогда раньше не видел эту задачу. - person Evgeny Kluev; 14.12.2011
comment
Он чем-то похож на «Ветвь-и-связь», но все же отличается. - person Evgeny Kluev; 14.12.2011
comment
Я думаю, что это в конечном итоге сделает это для меня. Для более коротких путей это чертовски быстро (он находит отличные пути из 8 узлов примерно за 40 секунд). Хитрость заключается в том, чтобы найти структуру данных, которая будет быстрой при вставке, быстрой при извлечении кратчайшего частичного пути и может быть ограничена несколькими элементами m. Если бы не этот последний, куча была бы идеальной. - person Plutor; 15.12.2011
comment
Наиболее естественным является кольцевой буфер ровно для m элементов. Один такой кольцевой буфер для каждого пути. - person Evgeny Kluev; 15.12.2011

Если количество возможных значений невелико, вы можете использовать алгоритм Флойда. с небольшой модификацией: для каждого пути вы сохраняете растровое изображение, представляющее различные уже посещенные значения. (В вашем случае растровое изображение будет иметь ширину 20 бит на путь.

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

person biziclop    schedule 09.12.2011
comment
Да, есть несколько значений, для которых растровое изображение имеет смысл. Алгоритм Флойда просто дает вам длину кратчайшего пути между узлами i и j или сам фактический путь? Я не понимаю, как вы получаете список всех узлов на пути из него (что меня волнует). - person Plutor; 10.12.2011
comment
@Plutor Базовый Floyd дает вам только длину, но на странице, на которую я ссылаюсь, они описывают, как расширить ее, чтобы также дать вам все фактические пути. - person biziclop; 10.12.2011
comment
Проблема с Floyd в том, что он дает мне кратчайший путь между двумя узлами. Это не гарантирует, что длина пути будет ровно n ребер. Возможно, мой (гигантский, сложный) набор данных не имеет кратчайших путей между двумя узлами, которые имеют достаточное количество шагов. - person Plutor; 13.12.2011
comment
@Плутор Это правда. Тогда это не очень хорошее решение. По крайней мере, идею растрового изображения можно использовать с большинством алгоритмов. - person biziclop; 13.12.2011

Пробовали ли вы прямолинейный подход и потерпели неудачу? Из вашего описания проблемы я не вижу причин для простого жадного алгоритма типа поиск в глубину может подойти:

  • Выберите начальный узел.
  • Проверьте непосредственных соседей, есть ли узлы, которые можно добавить к пути? Разверните путь с одним из них и повторите процесс для этого узла.
  • Если вы потерпите неудачу, вернитесь к последнему успешному состоянию и попробуйте нового соседа.
  • Если у вас закончились соседи для проверки, этот узел не может быть начальным узлом пути. Попробуйте новый.
  • Если у вас есть 10 узлов, все готово.

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

person Anders Lindahl    schedule 09.12.2011
comment
Проблема, как я уже сказал, в том, что мой набор данных на самом деле состоит из тысяч узлов с сотнями тысяч ребер. Этот алгоритм даст мне один допустимый путь, но не обязательно самый короткий. Мне пришлось бы перечислить все допустимые пути (или большое их количество), что могло бы занять... эээ... какое-то время. - person Plutor; 10.12.2011
comment
Сейчас я попробовал поиск в глубину, и это займет десятки тысяч процессоро-часов. - person Plutor; 11.12.2011

Похоже, что жадный поиск в глубину будет вашим лучшим выбором. Я думаю, что при разумном распределении значений атрибутов поиск единственной допустимой последовательности занимает время E[O(1)], то есть ожидаемое постоянное время. Я мог бы, вероятно, доказать это, но это может занять некоторое время. Доказательство будет использовать предположение о том, что существует ненулевая вероятность того, что допустимый следующий сегмент последовательности может быть найден на каждом шаге.

Жадный поиск будет возвращаться всякий раз, когда нарушается ограничение уникального значения атрибута. Поиск останавливается, когда найден путь из 15 сегментов. Если мы примем мою догадку о том, что каждую последовательность можно найти в E[O(1)], то нужно будет определить, сколько параллельных поисков нужно выполнить.

person Richard Povinelli    schedule 09.12.2011

Для тех, кто хочет поэкспериментировать, вот SQL-скрипт (postgres) для генерации поддельных данных.

SET search_path='tmp';

-- DROP TABLE nodes CASCADE;
CREATE TABLE nodes
    ( num INTEGER NOT NULL PRIMARY KEY
    , color INTEGER
    -- Redundant fields to flag {begin,end} of paths
    , is_root boolean DEFAULT false
    , is_terminal boolean DEFAULT false
    );

-- DROP TABLE edges CASCADE;
CREATE TABLE edges
    ( numfrom INTEGER NOT NULL REFERENCES nodes(num)
    , numto INTEGER NOT NULL REFERENCES nodes(num)
    , cost INTEGER NOT NULL DEFAULT 0
    );

-- Generate some nodes, set color randomly
INSERT INTO nodes (num)
SELECT n
FROM generate_series(1,2430) n
WHERE 1=1
    ;
UPDATE nodes SET COLOR= 1+TRUNC(20*random() );

-- (partial) cartesian product nodes*nodes. The ordering guarantees a DAG.
INSERT INTO edges(numfrom,numto,cost)
SELECT n1.num ,n2.num, 0
FROM nodes n1 ,nodes n2
WHERE n1.num < n2.num
AND random() < 0.029
    ;

UPDATE edges SET cost = 1+ 1000 * random();

ALTER TABLE edges
    ADD PRIMARY KEY (numfrom,numto)
    ;

ALTER TABLE edges
    ADD UNIQUE (numto,numfrom)
    ;

UPDATE nodes no SET is_root = true
WHERE NOT EXISTS (
    SELECT * FROM edges ed
    WHERE ed.numfrom = no.num
    );
UPDATE nodes no SET is_terminal = true
WHERE NOT EXISTS (
    SELECT * FROM edges ed
    WHERE ed.numto = no.num
    );

SELECT COUNT(*) AS nnode FROM nodes;
SELECT COUNT(*) AS nedge FROM edges;
SELECT color, COUNT(*) AS cnt FROM nodes GROUP BY color ORDER BY color;

SELECT COUNT(*) AS nterm FROM nodes no WHERE is_terminal = true;

SELECT COUNT(*) AS nroot FROM nodes no WHERE is_root = true;

WITH zzz AS    (
    SELECT numto, COUNT(*) AS fanin
    FROM edges
    GROUP BY numto
    )
SELECT zzz.fanin , COUNT(*) AS cnt
FROM zzz
GROUP BY zzz.fanin
ORDER BY zzz.fanin
    ;

WITH zzz AS    (
    SELECT numfrom, COUNT(*) AS fanout
    FROM edges
    GROUP BY numfrom
    )
SELECT zzz.fanout , COUNT(*) AS cnt
FROM zzz
GROUP BY zzz.fanout
ORDER BY zzz.fanout
    ;

COPY nodes(num,color,is_root,is_terminal)
TO '/tmp/nodes.dmp';

COPY edges(numfrom,numto, cost)
TO '/tmp/edges.dmp';
person wildplasser    schedule 15.12.2011

Проблема может быть решена с помощью динамического программирования следующим образом. Начнем с формального определения ее решения.

Учитывая DAG G = (V, E), пусть C будет набором цветов вершин, посещенных до сих пор, и пусть w[i, j] и c[i] будут соответственно весом (расстоянием), связанным с ребром (i, j), и цветом вершины i. Обратите внимание, что w[i, j] равно нулю, если ребро (i, j) не принадлежит E. Теперь задайте расстояние d для перехода от вершины i к вершине j с учетом C как

d[i, j, C] = w[i, j] if i is not equal to j and c[j] does not belong to C

           = 0 if i = j

           = infinite if i is not equal to j and c[j] belongs to C

Теперь мы готовы определить наши подзадачи следующим образом:

A[i, j, k, C] = кратчайший путь от i до j, который использует ровно k ребер и учитывает цвета в C, так что никакие две вершины на пути не окрашены в один и тот же цвет (один из цветов в C)

Пусть m будет максимальным количеством ребер, разрешенных на пути, и предположим, что вершины помечены 1, 2, ..., n. Пусть P[i,j,k] будет вершиной-предшественником j на кратчайшем пути, удовлетворяющем ограничениям от i до j. Следующий алгоритм решает проблему.

for k = 1 to m
  for i = 1 to n
    for j = 1 to n
      A[i,j,k,C] = min over x belonging to V {d[i,x,C] + A[x,j,k-1,C union c[x]]}
      P[i,j,k] = the vertex x that minimized A[i,j,k,C] in the previous statement

Установите начальные условия следующим образом:

A[i,j,k,C] = 0 for k = 0
A[i,j,k,C] = 0 if i is equal to j
A[i,j,k,C] = infinite in all of the other cases

Общая вычислительная сложность алгоритма O(m n^3); принимая во внимание, что в вашем конкретном случае m = 14 (поскольку вы хотите ровно 15 узлов), следует, что m = O(1) так что сложность на самом деле O(n^3). Для представления набора C используйте хеш-таблицу, чтобы для вставки и проверки членства требовалось в среднем O(1). Обратите внимание, что в алгоритме операция C union c[x] на самом деле является операцией вставки, в которой вы добавляете цвет вершины x в хэш-таблицу для C. Однако, поскольку вы вставляете только элемент, операция set union приводит к точно такой же результат (если цвета нет в наборе, он добавляется, иначе просто отбрасывается и набор не меняется). Наконец, для представления DAG используйте матрицу смежности.

После выполнения алгоритма, чтобы найти минимальный кратчайший путь среди всех возможных вершин i и j, просто найдите минимум среди значений A[i,j,m,C]. Обратите внимание: если это значение бесконечно, то допустимого кратчайшего пути не существует. Если допустимый кратчайший путь существует, то вы можете фактически определить его, используя значения P[i,j,k] и прослеживая в обратном направлении через предшествующие вершины. Например, начиная с a = P[i,j,m], последнее ребро кратчайшего пути — (a,j), предыдущее ребро — b = P[i,a,m-1], а его — (b,a) и так далее.

person Massimo Cafaro    schedule 19.12.2011