Skip to content

k_shortest_walk.hpp

SECTIONGraph INCLUDEnoya/k_shortest_walk.hpp

Return the lengths of the first k source-to-target directed walks. Reverse Dijkstra fixes one shortest-path tree toward the target. Every non-tree edge has a nonnegative sidetrack cost weight+dist[to]-dist[from]; a walk is its shortest base path plus an ordered sequence of sidetracks. Persistent leftist heaps collect all sidetracks available along each tree path. A final priority queue explores replacing a chosen sidetrack by a heap child or appending one after its destination, so walks are emitted in nondecreasing total length. Vertices and edges may repeat, including through zero-weight cycles.

Verified by k_shortest_walk.

按长度顺序求源点到终点的前 k 条游走,允许重复经过顶点和边。

Implementation

View on GitHub

#ifndef NOYA_K_SHORTEST_WALK_HPP
#define NOYA_K_SHORTEST_WALK_HPP 1

/// @complexity Time: O((V + E) log V + k log k).
/// Space: O(V + E log V + k).

#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {
namespace k_shortest_walk_internal {

template <class Weight> struct persistent_leftist_heap {
  struct node {
    Weight value{};
    int destination = -1;
    int left = -1;
    int right = -1;
    int rank = 1;
  };

  std::vector<node> nodes;

  int node_rank(int id) const { return id == -1 ? 0 : nodes[id].rank; }

  int meld(int first, int second) {
    if (first == -1 || second == -1) {
      return first == -1 ? second : first;
    }
    if (nodes[second].value < nodes[first].value) {
      std::swap(first, second);
    }
    node copy = nodes[first];
    copy.right = meld(copy.right, second);
    if (node_rank(copy.left) < node_rank(copy.right)) {
      std::swap(copy.left, copy.right);
    }
    copy.rank = node_rank(copy.right) + 1;
    nodes.push_back(copy);
    return int(nodes.size()) - 1;
  }

  int insert(int root, Weight value, int destination) {
    nodes.push_back(node{value, destination});
    return meld(root, int(nodes.size()) - 1);
  }
};

} // namespace k_shortest_walk_internal

/// @brief Return the lengths of the first k source-to-target directed walks.
/// Reverse Dijkstra fixes one shortest-path tree toward the target. Every
/// non-tree edge has a nonnegative sidetrack cost
/// `weight+dist[to]-dist[from]`; a walk is its shortest base path plus an
/// ordered sequence of sidetracks. Persistent leftist heaps collect all
/// sidetracks available along each tree path. A final priority queue explores
/// replacing a chosen sidetrack by a heap child or appending one after its
/// destination, so walks are emitted in nondecreasing total length. Vertices
/// and edges may repeat, including through zero-weight cycles.
template <class Weight>
std::vector<Weight> k_shortest_walk_lengths(
    int vertex_count,
    const std::vector<std::tuple<int, int, Weight>> &edges, int source,
    int target, int count) {
  assert(vertex_count >= 0);
  assert(0 <= source && source < vertex_count);
  assert(0 <= target && target < vertex_count);
  assert(count >= 0);
  if (count == 0) {
    return {};
  }

  struct adjacent_edge {
    int vertex;
    Weight weight;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> outgoing(vertex_count);
  std::vector<std::vector<adjacent_edge>> reversed(vertex_count);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [from, to, weight] = edges[id];
    assert(0 <= from && from < vertex_count);
    assert(0 <= to && to < vertex_count);
    assert(weight >= Weight{});
    outgoing[from].push_back({to, weight, id});
    reversed[to].push_back({from, weight, id});
  }

  const Weight infinity = std::numeric_limits<Weight>::max() / 4;
  std::vector<Weight> distance(vertex_count, infinity);
  std::vector<int> tree_edge(vertex_count, -1);
  std::vector<int> tree_parent(vertex_count, -1);
  using distance_entry = std::pair<Weight, int>;
  std::priority_queue<distance_entry, std::vector<distance_entry>,
                      std::greater<distance_entry>>
      dijkstra_queue;
  distance[target] = Weight{};
  dijkstra_queue.emplace(Weight{}, target);
  while (!dijkstra_queue.empty()) {
    auto [current_distance, vertex] = dijkstra_queue.top();
    dijkstra_queue.pop();
    if (current_distance != distance[vertex]) {
      continue;
    }
    for (auto [predecessor, weight, id] : reversed[vertex]) {
      Weight candidate = current_distance + weight;
      if (candidate < distance[predecessor]) {
        distance[predecessor] = candidate;
        tree_parent[predecessor] = vertex;
        tree_edge[predecessor] = id;
        dijkstra_queue.emplace(candidate, predecessor);
      }
    }
  }
  if (distance[source] == infinity) {
    return {};
  }

  std::vector<std::vector<int>> tree_children(vertex_count);
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    if (tree_parent[vertex] != -1) {
      tree_children[tree_parent[vertex]].push_back(vertex);
    }
  }

  using heap_type =
      k_shortest_walk_internal::persistent_leftist_heap<Weight>;
  heap_type heap;
  std::vector<int> heap_root(vertex_count, -1);
  std::vector<int> traversal{target};
  for (int index = 0; index < int(traversal.size()); index++) {
    int vertex = traversal[index];
    if (tree_parent[vertex] != -1) {
      heap_root[vertex] = heap_root[tree_parent[vertex]];
    }
    for (auto [to, weight, id] : outgoing[vertex]) {
      if (id == tree_edge[vertex] || distance[to] == infinity) {
        continue;
      }
      Weight sidetrack = weight + distance[to] - distance[vertex];
      heap_root[vertex] = heap.insert(heap_root[vertex], sidetrack, to);
    }
    traversal.insert(traversal.end(), tree_children[vertex].begin(),
                     tree_children[vertex].end());
  }

  using candidate = std::pair<Weight, int>;
  std::priority_queue<candidate, std::vector<candidate>,
                      std::greater<candidate>>
      candidates;
  candidates.emplace(distance[source], -1);
  std::vector<Weight> result;
  result.reserve(count);
  while (!candidates.empty() && int(result.size()) < count) {
    auto [total, node_id] = candidates.top();
    candidates.pop();
    result.push_back(total);

    int continuation_root;
    if (node_id == -1) {
      continuation_root = heap_root[source];
    } else {
      const auto &current = heap.nodes[node_id];
      for (int child : {current.left, current.right}) {
        if (child != -1) {
          candidates.emplace(total + heap.nodes[child].value - current.value,
                             child);
        }
      }
      continuation_root = heap_root[current.destination];
    }
    if (continuation_root != -1) {
      candidates.emplace(total + heap.nodes[continuation_root].value,
                         continuation_root);
    }
  }
  return result;
}

} // namespace noya

#endif // NOYA_K_SHORTEST_WALK_HPP
#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

/// @complexity Time: O((V + E) log V + k log k).
/// Space: O(V + E log V + k).

namespace noya {
namespace k_shortest_walk_internal {

template <class Weight> struct persistent_leftist_heap {
  struct node {
    Weight value{};
    int destination = -1;
    int left = -1;
    int right = -1;
    int rank = 1;
  };

  std::vector<node> nodes;

  int node_rank(int id) const { return id == -1 ? 0 : nodes[id].rank; }

  int meld(int first, int second) {
    if (first == -1 || second == -1) {
      return first == -1 ? second : first;
    }
    if (nodes[second].value < nodes[first].value) {
      std::swap(first, second);
    }
    node copy = nodes[first];
    copy.right = meld(copy.right, second);
    if (node_rank(copy.left) < node_rank(copy.right)) {
      std::swap(copy.left, copy.right);
    }
    copy.rank = node_rank(copy.right) + 1;
    nodes.push_back(copy);
    return int(nodes.size()) - 1;
  }

  int insert(int root, Weight value, int destination) {
    nodes.push_back(node{value, destination});
    return meld(root, int(nodes.size()) - 1);
  }
};

} // namespace k_shortest_walk_internal

/// @brief Return the lengths of the first k source-to-target directed walks.
/// Reverse Dijkstra fixes one shortest-path tree toward the target. Every
/// non-tree edge has a nonnegative sidetrack cost
/// `weight+dist[to]-dist[from]`; a walk is its shortest base path plus an
/// ordered sequence of sidetracks. Persistent leftist heaps collect all
/// sidetracks available along each tree path. A final priority queue explores
/// replacing a chosen sidetrack by a heap child or appending one after its
/// destination, so walks are emitted in nondecreasing total length. Vertices
/// and edges may repeat, including through zero-weight cycles.
template <class Weight>
std::vector<Weight> k_shortest_walk_lengths(
    int vertex_count,
    const std::vector<std::tuple<int, int, Weight>> &edges, int source,
    int target, int count) {
  assert(vertex_count >= 0);
  assert(0 <= source && source < vertex_count);
  assert(0 <= target && target < vertex_count);
  assert(count >= 0);
  if (count == 0) {
    return {};
  }

  struct adjacent_edge {
    int vertex;
    Weight weight;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> outgoing(vertex_count);
  std::vector<std::vector<adjacent_edge>> reversed(vertex_count);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [from, to, weight] = edges[id];
    assert(0 <= from && from < vertex_count);
    assert(0 <= to && to < vertex_count);
    assert(weight >= Weight{});
    outgoing[from].push_back({to, weight, id});
    reversed[to].push_back({from, weight, id});
  }

  const Weight infinity = std::numeric_limits<Weight>::max() / 4;
  std::vector<Weight> distance(vertex_count, infinity);
  std::vector<int> tree_edge(vertex_count, -1);
  std::vector<int> tree_parent(vertex_count, -1);
  using distance_entry = std::pair<Weight, int>;
  std::priority_queue<distance_entry, std::vector<distance_entry>,
                      std::greater<distance_entry>>
      dijkstra_queue;
  distance[target] = Weight{};
  dijkstra_queue.emplace(Weight{}, target);
  while (!dijkstra_queue.empty()) {
    auto [current_distance, vertex] = dijkstra_queue.top();
    dijkstra_queue.pop();
    if (current_distance != distance[vertex]) {
      continue;
    }
    for (auto [predecessor, weight, id] : reversed[vertex]) {
      Weight candidate = current_distance + weight;
      if (candidate < distance[predecessor]) {
        distance[predecessor] = candidate;
        tree_parent[predecessor] = vertex;
        tree_edge[predecessor] = id;
        dijkstra_queue.emplace(candidate, predecessor);
      }
    }
  }
  if (distance[source] == infinity) {
    return {};
  }

  std::vector<std::vector<int>> tree_children(vertex_count);
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    if (tree_parent[vertex] != -1) {
      tree_children[tree_parent[vertex]].push_back(vertex);
    }
  }

  using heap_type =
      k_shortest_walk_internal::persistent_leftist_heap<Weight>;
  heap_type heap;
  std::vector<int> heap_root(vertex_count, -1);
  std::vector<int> traversal{target};
  for (int index = 0; index < int(traversal.size()); index++) {
    int vertex = traversal[index];
    if (tree_parent[vertex] != -1) {
      heap_root[vertex] = heap_root[tree_parent[vertex]];
    }
    for (auto [to, weight, id] : outgoing[vertex]) {
      if (id == tree_edge[vertex] || distance[to] == infinity) {
        continue;
      }
      Weight sidetrack = weight + distance[to] - distance[vertex];
      heap_root[vertex] = heap.insert(heap_root[vertex], sidetrack, to);
    }
    traversal.insert(traversal.end(), tree_children[vertex].begin(),
                     tree_children[vertex].end());
  }

  using candidate = std::pair<Weight, int>;
  std::priority_queue<candidate, std::vector<candidate>,
                      std::greater<candidate>>
      candidates;
  candidates.emplace(distance[source], -1);
  std::vector<Weight> result;
  result.reserve(count);
  while (!candidates.empty() && int(result.size()) < count) {
    auto [total, node_id] = candidates.top();
    candidates.pop();
    result.push_back(total);

    int continuation_root;
    if (node_id == -1) {
      continuation_root = heap_root[source];
    } else {
      const auto &current = heap.nodes[node_id];
      for (int child : {current.left, current.right}) {
        if (child != -1) {
          candidates.emplace(total + heap.nodes[child].value - current.value,
                             child);
        }
      }
      continuation_root = heap_root[current.destination];
    }
    if (continuation_root != -1) {
      candidates.emplace(total + heap.nodes[continuation_root].value,
                         continuation_root);
    }
  }
  return result;
}

} // namespace noya