10

I've got a directed graph (N, A), where each node n[i] has a value v[i] and a threshold t[i]. For each arrow (n[i], n[j]), the invariant v[i] <= v[j] holds. I need to efficiently implement the following operations:

  • increaseThreshold(i, x): Set t[i] = max(t[i], x). That's trivial and only for completeness here.
  • increaseValue(i, x): Set v[i] = max(v[i], x) and increase other values as needed so that the above invariant holds.
  • evaluate(i): return true if v[i] < t[i]

The most straightforward implementation would store v[i], t[i], and the outgoing arrows with each node. On increaseValue(i, x), it'd propagate the value along all outgoing arrows (using a set of "open" nodes like many other graph algorithms do). With v[i] stored with each node, evaluate(i) is trivial.

As increaseValue is much more frequent than the other operations, this eager approach seems to be wasteful. So I wonder, if some lazy propagation where v[i] is recomputed as needed could be more efficient. For this, I'd maintain w[i] as the maximum of all x from increaseValue(i, x) and compute v[j] on the fly when evaluate(j) needs it. It can be computed as the maximum of w[i] over all nodes n[i] from which there's a path to n[j]. Actually, once I know that v[j] >= t[j], the exact value v[j] doesn't matter and I can stop the computation.

Unfortunately, this lazy algorithm is very inefficient, so it doesn't pay off even with increaseValue being orders of magnitude more frequent than evaluate.

I imagine, some "partially lazy" algorithm could be better, but that's just my intuition and I can't make any progress with it.

Is this somehow a well-known problem? Any other idea?

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • Do you need an online algorithm? – xrisk Sep 10 '17 at 21:29
  • @Rishav Yes, the operations will be repeated again and again. – maaartinus Sep 10 '17 at 21:36
  • 1
    Why `increaseThreshold` has to propagate? It may update t[i] but why it influences other nodes? – algrid Sep 10 '17 at 21:42
  • What is the number of nodes you are looking to potentially deal with? – xrisk Sep 10 '17 at 21:51
  • @algrid Thanks, fixed, it was a "mental typo". – maaartinus Sep 10 '17 at 22:11
  • @Rishav Maybe tens of thousands with maybe ten arrows per node. – maaartinus Sep 10 '17 at 22:12
  • @maaartinus Why do you consider your lazy approach inefficient? It makes `evaluate(i)` longer but `increaseValue` is very fast. When you're doing `evaluate(i)` you could also do kind of propagation but considering only those nodes with `w[i] != v[i]` and updating v[i] simultaneously. In this scenario the second call for `evaluate(i)` with the same argument will work almost instantly (with no `increaseValue` calls in between). – algrid Sep 10 '17 at 22:26
  • @algrid Each `evaluate` has to look at each node from which there's a path and this may be a substantial portion of the graph. When the values don't get propagated immediately, then you know nothing about what may have changed and you must inspect a lot of nodes. – maaartinus Sep 10 '17 at 22:30
  • @maaartinus ah, you're right it's not enough to consider nodes with `w[i] != v[i]` only – algrid Sep 10 '17 at 22:32
  • 1
    You said that an edge n[i] -> n[j] means that v[i] >= v[j]. Shouldn't that be v[j] >= v[i]? Otherwise I don't understand the propagation. – Matt Timmermans Sep 11 '17 at 02:20
  • @MattTimmermans Thanks, fixed (it's sad that I've made so many mistakes in such a short post). – maaartinus Sep 11 '17 at 20:15
  • How do your determine the nodes that have a path to `n[j]` in eval in the lazy approach? Is that something that could be optimized? – Stefan Haustein Nov 11 '17 at 15:19
  • @StefanHaustein I guess, no. I can store the outgoing and/or the incoming arrows with each node as needed. In the lazy approach, I'd do a depth-first search with tracking of already visited nodes. That's simple and efficient, except for the fact that there are too many nodes. – maaartinus Nov 11 '17 at 15:27
  • Is Threshold also increasing? – Luai Ghunim Nov 17 '17 at 06:25
  • @LuaiGhunim Sure, the only operation modifying it is `increaseThreshold`. It gets never smaller, but unlike the value, there's no relation between thresholds of connected nodes. – maaartinus Nov 17 '17 at 06:31

2 Answers2

3

How about holding back the propagating of the increment until you need to evaluate? increaseValue would only update the value, mark the node as dirty and add it to a set of dirty nodes.

When you need to evaluate, propagate the increments for all changed nodes, starting with the largest new value. This should save propagating multiple increments for the same node and potentially nodes on paths (which might get validated on the go)?

Stefan Haustein
  • 18,427
  • 3
  • 36
  • 51
  • Nice! That's surely a good idea and simple to implement. It can be slightly refined by stopping when the value is smaller than the interesting threshold. This way the total amount of work is minimized. +++ It's pretty flexible: A background thread could poll the priority queue so that `evaluate` would not be slowed down by all the work (worsening the total amount of work but improving latency). – maaartinus Nov 11 '17 at 17:05
1

I've got a simple idea for the "partially lazy" algorithm (no solution, just an idea).

Let's call the "most straightforward implementation" from my question the telling algorithm as each node tells its successors what to do. Let's rename the "lazy algorithm" to asking algorithm as each node asks its predecessors if there's something to do.

The arrows can be partitioned into telling and asking ones. All the telling arrows get processed after increase, while the asking arrows wait until evaluate. I guess, this partitioning can't be arbitrary: For any two arrows (n[i], n[j]) and (n[j], n[k]), I can't imagine how to process when the former is asking and the latter is telling, so this case must be forbidden.

This may help a lot when there are many nodes with only incoming arrows which gets evaluated rarely, which seems to be the case.

It can be probably combined with the idea from the other answer.

maaartinus
  • 44,714
  • 32
  • 161
  • 320