Лекция 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 независимых очередей с локами. Операции пусть будут работать над случайно выбранными очередями.

  • Добавление: добавляем в случайную очередь
  • Удаление: берем две случайные очереди и удаляем из лучшей.