Лекция 3.2. Relaxed Algorithms.
BFS
Вспомним обычный BFS:
val Q = Queue<Node>()
start.distance = 0
Q.add(start)
while Q.isNotEmpty() {
u := Q.remove()
d := u.distance
for (v : u.edges) {
if v.distance != INF: continue
v.distance = d + 1
Q.add(v)
}
}
Просто заменить Queue на ConcurrentQueue не поможет, т.к. первый поток удалит первую вершину из очереди, все остальные потоки выйдут из цикла и алгоритм станет однопоточным.
Можем считать число вершин в обработке вместо Q.isNotEmpty(). Тогда есть другая проблема: мы не гарантируем порядок обработки вершин, это никакой не BFS. Но нас это не очень и волнует, ибо мы будем обновлять расстояние только если оно меньше того, которое уже посчитано:
val Q = Queue<Node>()
start.distance = 0
Q.add(start)
activeNodes = 1
while activeNodes > 0 {
u := Q.remove()
d := u.distance
for (v : u.edges) {
if v.updateDistIfLower(d + 1) {
v.distance = d + 1
Q.add(v)
activeNodes++
}
}
activeNodes--
}
С одной стороны, мы одну вершину обрабатываем одну вершину несколько раз, но с другой — мы параллельны. На реальных графах это профит.
Алгоритм Дейкстры
Дейкстра это примерно то же самое, что у нас, только у нас приоритетная очередь. Быстрая приоритетная очередь это миф, лучший случай это куча с локами. Но мы можем написать почти приоритетную очередь. Тогда алгоритм всё ещё будет верным, но у нас опять будут инверсии.
Multi-Queue
Возьмём T независимых очередей с локами. Операции пусть будут работать над случайно выбранными очередями.
- Добавление: добавляем в случайную очередь
- Удаление: берем две случайные очереди и удаляем из лучшей.