I came across this problem quite recently.
Suppose there are n points on x-axis, x[0],x[1] .. x[n-1].
Let the weight associated with each of these points be w[0],w[1] .. w[n-1].
Starting from any point between 0 to n-1, the objective is to cover all the points such that the sum of w[i]*d[i] is minimized where d[i] is the distance covered to reach the ith point from the starting point.
Example:
Suppose the points are: 1 5 10 20 40
Suppose the weights are: 1 2 10 50 13
If I choose to start at point 10 and choose to move to point 20 then to 5 then to 40 and then finally to 1, then the weighted sum will become 10*0+50*(10)+2*(10+15)+13*(10+15+35)+1*(10+15+35+39).
I have tried to solve it using greedy approach by starting off from the point which has maximum associated weight and move to second maximum weight point and so on. But the algorithm does not work. Can someone give me pointers about the approach which must be taken to solve this problem?
There's a very important fact that leads to a polynomial time algorithm:
Since the points are located on some axis, they generate a path graph, which means that for every 3 vertices v1,v2,v3, if v2 is between v1 and v3, then the distance between v1 and v3 equals the distance between v1 and v2 plus the distance between v2 and v3. therefor if for example we start at v1, the obj. function value of a path that goes first to v2 and then to v3 will always be less than the value of the path that first goes to v3 and then back to v2 because:
value of the first path = w[2]*D(v1,v2)+W[3]*(D(v1,v2)+D(v2,v3))
value of the second path = w[3]*D(v1,v3)+W[2]*((v1,v3)+D(v3,v2)) = w[3]*D(v1,v2)+w[3]*D(v2,v3)+w[2]*(D(v1,v2)+2*D(v3,v2))
If we subtract the first path value from the second, we are left with w[2]*2*D(v3,v2) which is equal to or greater than 0 unless you consider negative weights.
All this means that if we are located at a certain point, there are always only 2 options we should consider: going to closest unvisited point on the left or the closest unvisited point on the right.
This is very significant as it leaves us with 2^n possible paths rather than n! possible paths (like in the Travelling Salesman Problem).
Solving the TSP/minimum weight hamiltonian path on path graphs can be done in polynomial time using dynamic programming, you should apply the exact same method but modify the way you calculated the objective function.
Since you don't know the starting vertex, you'll have to run this algorithm n time, each time starting from a different vertex, which means the running time will be multiplied by n.
Maybe you should elaborate what you mean that the algorithm "does not work". The basic idea of the greedy approach that you described seems feasible for me. Do you mean that the greedy approach will not necessarily find the optimal solution? As it was pointed out in the comments, this might be an NP-complete problem - although, to be sure, one would have to analyze it further: Some dynamic programming, and maybe some prefix sums for the distance computations could lead to a polynomial time solution as well.
I quickly implemented the greedy solution in Java (hopefully I understood everything correctly...)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class MinWeightSum
{
public static void main(String[] args)
{
double x[] = { 1, 5, 10, 20, 40 };
double w[] = { 1, 2, 10, 50, 13 };
List<Integer> givenIndices = Arrays.asList(2, 3, 1, 4, 0);
Path path = createPath(x, w, givenIndices);
System.out.println("Initial result "+path.sum);
List<Integer> sortedWeightIndices =
computeSortedWeightIndices(w);
Path greedyPath = createPath(x, w, sortedWeightIndices);
System.out.println("Greedy result "+greedyPath.sum);
System.out.println("For "+sortedWeightIndices+" sum "+greedyPath.sum);
}
private static Path createPath(
double x[], double w[], List<Integer> indices)
{
Path path = new Path(x, w);
for (Integer i : indices)
{
path.append(i);
}
return path;
}
private static List<Integer> computeSortedWeightIndices(final double w[])
{
List<Integer> indices = new ArrayList<Integer>();
for (int i=0; i<w.length; i++)
{
indices.add(i);
}
Collections.sort(indices, new Comparator<Integer>()
{
#Override
public int compare(Integer i0, Integer i1)
{
return Double.compare(w[i1], w[i0]);
}
});
return indices;
}
static class Path
{
double x[];
double w[];
int prevIndex = -1;
double distance;
double sum;
Path(double x[], double w[])
{
this.x = x;
this.w = w;
}
void append(int index)
{
if (prevIndex != -1)
{
distance += Math.abs(x[prevIndex]-x[index]);
}
sum += w[index] * distance;
prevIndex = index;
}
}
}
The sequence of indices that you described in the example yields the solution
For [2, 3, 1, 4, 0] sum 1429.0
The greedy approach that you described gives
For [3, 4, 2, 1, 0] sum 929.0
The best solution is
For [3, 2, 4, 1, 0] sum 849.0
which I found by checking all permutations of indices (This is not feasible for larger n, of course)
Suppose you are part way through a solution and have traveled for distance D so far. If you go a further distance x and see a point with weight w it costs you (D + x)w. If you go a further distance y and see a point with weight v it costs you (D + x + y)v.. If you sum all of this up there is a component that depends on the path you take after the distance D: xw + xv + yv+..., and there is a component that depends on distance D and the sum of the weights of the points that you need to carry: D (v + w + ...). But the component that depends on distance D does not depend on anything else except the sum of the weights of the points you need to visit, so it is fixed, in the sense that it is the same regardless of the path you take after going distance D.
It always make sense to visit points we pass as we visit them, so the best path will start off with a single point (possibly at the edge of the set of points to be visited and possibly in the centre) and then expand this to an interval of visited points, and then expand this to visit all the points. To pre-calculate the relative costs of visiting all points outside the interval we only need to know the current position and the size of the interval, not the distance travelled so far.
So an expensive but polynomial dynamic programming approach has as the state the current position (which must be one of the points) the position of the first, if any, unvisited point to the left of the current position, and the position, if any, of the first unvisited point to the right of the current point. There are at most two points we should consider visiting next - the point to the right of the current point and the point to the left of the current point. We can work out the cost of these two alternatives by looking at pre-computed costs for states with fewer points left, and store the best result as the best possible cost from this point. We could compute these costs under the fiction that D=0 at the time we reach the current point. When we look up stored costs they are also stored under this assumption (but with D=0 at their current point, not our current point), but we know the sum of the weights of points left at that stage, so we can add to the stored cost that sum of weights times the distance between our current point and the point we are looking up costs for to compensate for this.
That gives cost O(n^3), because you are building a table with O(n^3) cells, with each cell the product of a relatively simple process. However, because it never makes sense to pass cells without visiting them, the current point must be next to one of the two points at either end of the interval, so we need consider only O(n^2) possibilities, which cuts the cost down to O(n^2). A zig-zag path such as (0, 1, -1, 2, -2, 3, -3, 4, -4...) might be the best solution for suitably bizarre weights, but it is still the case, even for instance when going from -2 to 3, that -2 to is the closest point not yet taken between the two points 3 and -3.
I have put an attempted java implementation at http://www.mcdowella.demon.co.uk/Plumber.java. The test harness checks this DP version against a (slow) almost exhaustive version for a number of randomly generated test cases of length up to and including 12. It still may not be completely bug-free, but hopefully it will fill in the details.
Related
I am trying to implement a stochastic ant colony optimisation algorithm, and I'm having trouble working out how to implement movement choices based on probabilities.
the standard (greedy) version that I have implemented so far is that an ant m at a vertex i on a graph G = (V,E) where E is the set of edges (i, j), will choose the next vertex j based on the following criteria:
j = argmax(<fitness function for j>)
such that j is connected to i
the problem I am having is in trying to implement a stochastic version of this, so that now the criteria for choosing a new vertex, j is:
P(j) = <fitness function for j>/sum(<fitness function for J>)
where P(j) is the probability of choosing vertex j,
such j is connected to i,
and J is the set of all vertices connected to i
I understand the mathematics behind it, I am just having trouble working out how i should actually implement it.
if, say, i have 3 vertices connected to i, each with a probability of 0.2, 0.3, 0.5 - what is the best way to make the selection? should I just randomly select a vertex j, then generate a random number r in the range (0,1) and if r >= P(j), select vertex j? or is there a better way?
Looking at the problem statement, I think you are not trying to visit all nodes (connected to i (say) ), but some of the nodes based on some probability distribution. Lets take an example:
You have a node i and connected to it are 5 nodes, a1...a5, with probabilities p1...p5, such that sum(p_i) = 1. No, say the precision of probabilities that you consider is 2 places after decimal. Also, you dont want to visit all 5 nodes, but only k of them. Lets say, in this example, k = 2. So, since 2 places of decimal is your probability precision, add 3 to it to increase normality of probability distribution in the random function. (You can change this 3 to any number of your choice, as far as performance is concerned) (Since you have not tagged any language, I'll take example of java's nextInt() function to generate random numbers.)
Lets give some values:
p1...p5 = {0.17, 0.11, 0.45, 0.03, 0.24}
Now, in a loop from 1 to k, generate a random number from (0...10^5). {5 = 2 + 3, ie. precision + 3}. If the generated number is from 0 to 16999, go with node a1, 17000 to 27999, go with a2, 28000 to 72999, go with a3...and so on. You get the idea.
What you're trying to implement is a weighted random choice depending on the probabilities for the components of the solution, or a random proportional selection rule on ACO terms. Here is an snippet of the implementation of this rule on the Isula Framework:
double value = random.nextDouble();
while (componentWithProbabilitiesIterator.hasNext()) {
Map.Entry<C, Double> componentWithProbability = componentWithProbabilitiesIterator
.next();
Double probability = componentWithProbability.getValue();
total += probability;
if (total >= value) {
nextNode = componentWithProbability.getKey();
getAnt().visitNode(nextNode);
return true;
}
}
You just need to generate a random value between 0 and 1 (stored in value), and start accumulating the probabilities of the components (on the total variable). When the total exceeds the threshold defined in value, we have found the component to add to the solution.
I came across this problem quite recently.
Suppose there are n points on x-axis, x[0],x[1] .. x[n-1].
Let the weight associated with each of these points be w[0],w[1] .. w[n-1].
Starting from any point between 0 to n-1, the objective is to cover all the points such that the sum of w[i]*d[i] is minimized where d[i] is the distance covered to reach the ith point from the starting point.
Example:
Suppose the points are: 1 5 10 20 40
Suppose the weights are: 1 2 10 50 13
If I choose to start at point 10 and choose to move to point 20 then to 5 then to 40 and then finally to 1, then the weighted sum will become 10*0+50*(10)+2*(10+15)+13*(10+15+35)+1*(10+15+35+39).
I have tried to solve it using greedy approach by starting off from the point which has maximum associated weight and move to second maximum weight point and so on. But the algorithm does not work. Can someone give me pointers about the approach which must be taken to solve this problem?
There's a very important fact that leads to a polynomial time algorithm:
Since the points are located on some axis, they generate a path graph, which means that for every 3 vertices v1,v2,v3, if v2 is between v1 and v3, then the distance between v1 and v3 equals the distance between v1 and v2 plus the distance between v2 and v3. therefor if for example we start at v1, the obj. function value of a path that goes first to v2 and then to v3 will always be less than the value of the path that first goes to v3 and then back to v2 because:
value of the first path = w[2]*D(v1,v2)+W[3]*(D(v1,v2)+D(v2,v3))
value of the second path = w[3]*D(v1,v3)+W[2]*((v1,v3)+D(v3,v2)) = w[3]*D(v1,v2)+w[3]*D(v2,v3)+w[2]*(D(v1,v2)+2*D(v3,v2))
If we subtract the first path value from the second, we are left with w[2]*2*D(v3,v2) which is equal to or greater than 0 unless you consider negative weights.
All this means that if we are located at a certain point, there are always only 2 options we should consider: going to closest unvisited point on the left or the closest unvisited point on the right.
This is very significant as it leaves us with 2^n possible paths rather than n! possible paths (like in the Travelling Salesman Problem).
Solving the TSP/minimum weight hamiltonian path on path graphs can be done in polynomial time using dynamic programming, you should apply the exact same method but modify the way you calculated the objective function.
Since you don't know the starting vertex, you'll have to run this algorithm n time, each time starting from a different vertex, which means the running time will be multiplied by n.
Maybe you should elaborate what you mean that the algorithm "does not work". The basic idea of the greedy approach that you described seems feasible for me. Do you mean that the greedy approach will not necessarily find the optimal solution? As it was pointed out in the comments, this might be an NP-complete problem - although, to be sure, one would have to analyze it further: Some dynamic programming, and maybe some prefix sums for the distance computations could lead to a polynomial time solution as well.
I quickly implemented the greedy solution in Java (hopefully I understood everything correctly...)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class MinWeightSum
{
public static void main(String[] args)
{
double x[] = { 1, 5, 10, 20, 40 };
double w[] = { 1, 2, 10, 50, 13 };
List<Integer> givenIndices = Arrays.asList(2, 3, 1, 4, 0);
Path path = createPath(x, w, givenIndices);
System.out.println("Initial result "+path.sum);
List<Integer> sortedWeightIndices =
computeSortedWeightIndices(w);
Path greedyPath = createPath(x, w, sortedWeightIndices);
System.out.println("Greedy result "+greedyPath.sum);
System.out.println("For "+sortedWeightIndices+" sum "+greedyPath.sum);
}
private static Path createPath(
double x[], double w[], List<Integer> indices)
{
Path path = new Path(x, w);
for (Integer i : indices)
{
path.append(i);
}
return path;
}
private static List<Integer> computeSortedWeightIndices(final double w[])
{
List<Integer> indices = new ArrayList<Integer>();
for (int i=0; i<w.length; i++)
{
indices.add(i);
}
Collections.sort(indices, new Comparator<Integer>()
{
#Override
public int compare(Integer i0, Integer i1)
{
return Double.compare(w[i1], w[i0]);
}
});
return indices;
}
static class Path
{
double x[];
double w[];
int prevIndex = -1;
double distance;
double sum;
Path(double x[], double w[])
{
this.x = x;
this.w = w;
}
void append(int index)
{
if (prevIndex != -1)
{
distance += Math.abs(x[prevIndex]-x[index]);
}
sum += w[index] * distance;
prevIndex = index;
}
}
}
The sequence of indices that you described in the example yields the solution
For [2, 3, 1, 4, 0] sum 1429.0
The greedy approach that you described gives
For [3, 4, 2, 1, 0] sum 929.0
The best solution is
For [3, 2, 4, 1, 0] sum 849.0
which I found by checking all permutations of indices (This is not feasible for larger n, of course)
Suppose you are part way through a solution and have traveled for distance D so far. If you go a further distance x and see a point with weight w it costs you (D + x)w. If you go a further distance y and see a point with weight v it costs you (D + x + y)v.. If you sum all of this up there is a component that depends on the path you take after the distance D: xw + xv + yv+..., and there is a component that depends on distance D and the sum of the weights of the points that you need to carry: D (v + w + ...). But the component that depends on distance D does not depend on anything else except the sum of the weights of the points you need to visit, so it is fixed, in the sense that it is the same regardless of the path you take after going distance D.
It always make sense to visit points we pass as we visit them, so the best path will start off with a single point (possibly at the edge of the set of points to be visited and possibly in the centre) and then expand this to an interval of visited points, and then expand this to visit all the points. To pre-calculate the relative costs of visiting all points outside the interval we only need to know the current position and the size of the interval, not the distance travelled so far.
So an expensive but polynomial dynamic programming approach has as the state the current position (which must be one of the points) the position of the first, if any, unvisited point to the left of the current position, and the position, if any, of the first unvisited point to the right of the current point. There are at most two points we should consider visiting next - the point to the right of the current point and the point to the left of the current point. We can work out the cost of these two alternatives by looking at pre-computed costs for states with fewer points left, and store the best result as the best possible cost from this point. We could compute these costs under the fiction that D=0 at the time we reach the current point. When we look up stored costs they are also stored under this assumption (but with D=0 at their current point, not our current point), but we know the sum of the weights of points left at that stage, so we can add to the stored cost that sum of weights times the distance between our current point and the point we are looking up costs for to compensate for this.
That gives cost O(n^3), because you are building a table with O(n^3) cells, with each cell the product of a relatively simple process. However, because it never makes sense to pass cells without visiting them, the current point must be next to one of the two points at either end of the interval, so we need consider only O(n^2) possibilities, which cuts the cost down to O(n^2). A zig-zag path such as (0, 1, -1, 2, -2, 3, -3, 4, -4...) might be the best solution for suitably bizarre weights, but it is still the case, even for instance when going from -2 to 3, that -2 to is the closest point not yet taken between the two points 3 and -3.
I have put an attempted java implementation at http://www.mcdowella.demon.co.uk/Plumber.java. The test harness checks this DP version against a (slow) almost exhaustive version for a number of randomly generated test cases of length up to and including 12. It still may not be completely bug-free, but hopefully it will fill in the details.
If I am finding the roots of a polynomial using the bisection method, and in some cases depending on the polynomial the roots might be negative or they may be positive.
I understand I can determine if the roots are going to be negative or positive, based on the result of evaluating the polynomial... however I am unsure what I would use as x.
Can anyone give any insight here?
The fact that the roots can be negative or positive has nothing to do with the bisection method. The existence of a root can be proved using the intermediate value theorem from calculus.
So all you have to do is find points x1 and x2 such that y(x1) is negative and y(x2) is positive. Then you know from the IVT that there is a root between x1 and x2. You do that by doing a binary search on that interval. If y(x3) = y((x1+x2)/2) is negative, then you repeat the bisection search on the interval [x3,x2]. Otherwise if it's positive, then search on the interval [x1,x3].
It doesn't matter whether the root is negative or positive. I'm not sure if that answers your question, but I hope that helps you understand the algorithm.
Many root-finders allow the user to supply a starting point or points to begin searches. This allows users to try to "fiddle" the results to find different roots or allow the finder to converge to a root.
If it does not make sense to allow a user to provide starting values you could begin by probing a handful of points:
-1, 0, 1
-10, 0, 10
-100, 0, 100
etc.
If the input is an odd polynomial, this will eventually discover a suitable range for bisection. If the input is an even polynomial, you might never catch sign changes (consider f(x)=x^2 -- it is never negative), so be prepared to give up after a certain (configurable?) amount of probing.
I've suggested making the ranges larger by powers of 10 here; since the bisection approach cuts the range in half each time, perhaps this is too conservative. (It'll take between two and three iterations of the bisection to reduce a range to the next "tighter" bracket.) Maybe better would be larger jumps:
-10, 0, 1
-1000, 0, 1000
-100000, 0, 100000
etc.
This will perform fewer probes but require more bisection. Try a handful of polynomials and track execution time for finding the roots with both suggestions.
You would probably find this helpful.
using System;
namespace Bisection_Method
{
class Program
{
public double midPoint (double xl, double xu)
{
return (xl + xu) / 2;
}
public double function(double x)
{
return (x*x-2);
}
static void Main(string[] args)
{
Program root = new Program();
double xm=0, xl=1, xu=2, check=0;
for (int x = 0; x < 20; x++)
{
xm = (xl + xu) / 2;
check = root.function(xl) * root.function(xm);
if (check < 0)
xu = xm;
else if (check > 0)
xl = xm;
else if (check == 0)
{
break;
}
}
Console.WriteLine("The Approximate of the Root is {0}", xm);
}
}
}
http://mustafa.amnbytes.com/2012/09/bisection-method-program-in-c.html
In order to use the bisection algorithm, you first need to find an interval which contains a root. The standard algorithm for this is given in Sturm's Theorem.
However the standard bisection algorithm expects the signs of the function values in the endpoints to be different. This can be problem. The simplest example is x^2 which has the single root 0 of order 2. Since x^2 is positive for all non-zero x, you can not find an interval enclosing the root suitable for use with the bisection algorithm.
In my particular case, the graph is represented as an adjacency list and is undirected and sparse, n can be in the millions, and d is 3. Calculating A^d (where A is the adjacency matrix) and picking out the non-zero entries works, but I'd like something that doesn't involve matrix multiplication. A breadth-first search on every vertex is also an option, but it is slow.
def find_d(graph, start, st, d=0):
if d == 0:
st.add(start)
else:
st.add(start)
for edge in graph[start]:
find_d(graph, edge, st, d-1)
return st
graph = { 1 : [2, 3],
2 : [1, 4, 5, 6],
3 : [1, 4],
4 : [2, 3, 5],
5 : [2, 4, 6],
6 : [2, 5]
}
print find_d(graph, 1, set(), 2)
Let's say that we have a function verticesWithin(d,x) that finds all vertices within distance d of vertex x.
One good strategy for a problem such as this, to expose caching/memoisation opportunities, is to ask the question: How are the subproblems of this problem related to each other?
In this case, we can see that verticesWithin(d,x) if d >= 1 is the union of vertices(d-1,y[i]) for all i within range, where y=verticesWithin(1,x). If d == 0 then it's simply {x}. (I'm assuming that a vertex is deemed to be of distance 0 from itself.)
In practice you'll want to look at the adjacency list for the case d == 1, rather than using that relation, to avoid an infinite loop. You'll also want to avoid the redundancy of considering x itself as a member of y.
Also, if the return type of verticesWithin(d,x) is changed from a simple list or set, to a list of d sets representing increasing distance from x, then
verticesWithin(d,x) = init(verticesWithin(d+1,x))
where init is the function that yields all elements of a list except the last one. Obviously this would be a non-terminating recursive relation if transcribed literally into code, so you have to be a little bit clever about how you implement it.
Equipped with these relations between the subproblems, we can now cache the results of verticesWithin, and use these cached results to avoid performing redundant traversals (albeit at the cost of performing some set operations - I'm not entirely sure that this is a win). I'll leave it as an exercise to fill in the implementation details.
You already mention the option of calculating A^d, but this is much, much more than you need (as you already remark).
There is, however, a much cheaper way of using this idea. Suppose you have a (column) vector v of zeros and ones, representing a set of vertices. The vector w := A v now has a one at every node that can be reached from the starting node in exactly one step. Iterating, u := A w has a one for every node you can reach from the starting node in exactly two steps, etc.
For d=3, you could do the following (MATLAB pseudo-code):
v = j'th unit vector
w = v
for i = (1:d)
v = A*v
w = w + v
end
the vector w now has a positive entry for each node that can be accessed from the jth node in at most d steps.
Breadth first search starting with the given vertex is an optimal solution in this case. You will find all the vertices that within the distance d, and you will never even visit any vertices with distance >= d + 2.
Here is recursive code, although recursion can be easily done away with if so desired by using a queue.
// Returns a Set
Set<Node> getNodesWithinDist(Node x, int d)
{
Set<Node> s = new HashSet<Node>(); // our return value
if (d == 0) {
s.add(x);
} else {
for (Node y: adjList(x)) {
s.addAll(getNodesWithinDist(y,d-1);
}
}
return s;
}
I'm trying to work out an algorithm for finding a path across a directed graph. It's not a conventional path and I can't find any references to anything like this being done already.
I want to find the path which has the maximum minimum weight.
I.e. If there are two paths with weights 10->1->10 and 2->2->2 then the second path is considered better than the first because the minimum weight (2) is greater than the minimum weight of the first (1).
If anyone can work out a way to do this, or just point me in the direction of some reference material it would be incredibly useful :)
EDIT:: It seems I forgot to mention that I'm trying to get from a specific vertex to another specific vertex. Quite important point there :/
EDIT2:: As someone below pointed out, I should highlight that edge weights are non negative.
I am copying this answer and adding also adding my proof of correctness for the algorithm:
I would use some variant of Dijkstra's. I took the pseudo code below directly from Wikipedia and only changed 5 small things:
Renamed dist to width (from line 3 on)
Initialized each width to -infinity (line 3)
Initialized the width of the source to infinity (line 8)
Set the finish criterion to -infinity (line 14)
Modified the update function and sign (line 20 + 21)
1 function Dijkstra(Graph, source):
2 for each vertex v in Graph: // Initializations
3 width[v] := -infinity ; // Unknown width function from
4 // source to v
5 previous[v] := undefined ; // Previous node in optimal path
6 end for // from source
7
8 width[source] := infinity ; // Width from source to source
9 Q := the set of all nodes in Graph ; // All nodes in the graph are
10 // unoptimized – thus are in Q
11 while Q is not empty: // The main loop
12 u := vertex in Q with largest width in width[] ; // Source node in first case
13 remove u from Q ;
14 if width[u] = -infinity:
15 break ; // all remaining vertices are
16 end if // inaccessible from source
17
18 for each neighbor v of u: // where v has not yet been
19 // removed from Q.
20 alt := max(width[v], min(width[u], width_between(u, v))) ;
21 if alt > width[v]: // Relax (u,v,a)
22 width[v] := alt ;
23 previous[v] := u ;
24 decrease-key v in Q; // Reorder v in the Queue
25 end if
26 end for
27 end while
28 return width;
29 endfunction
Some (handwaving) explanation why this works: you start with the source. From there, you have infinite capacity to itself. Now you check all neighbors of the source. Assume the edges don't all have the same capacity (in your example, say (s, a) = 300). Then, there is no better way to reach b then via (s, b), so you know the best case capacity of b. You continue going to the best neighbors of the known set of vertices, until you reach all vertices.
Proof of correctness of algorithm:
At any point in the algorithm, there will be 2 sets of vertices A and B. The vertices in A will be the vertices to which the correct maximum minimum capacity path has been found. And set B has vertices to which we haven't found the answer.
Inductive Hypothesis: At any step, all vertices in set A have the correct values of maximum minimum capacity path to them. ie., all previous iterations are correct.
Correctness of base case: When the set A has the vertex S only. Then the value to S is infinity, which is correct.
In current iteration, we set
val[W] = max(val[W], min(val[V], width_between(V-W)))
Inductive step: Suppose, W is the vertex in set B with the largest val[W]. And W is dequeued from the queue and W has been set the answer val[W].
Now, we need to show that every other S-W path has a width <= val[W]. This will be always true because all other ways of reaching W will go through some other vertex (call it X) in the set B.
And for all other vertices X in set B, val[X] <= val[W]
Thus any other path to W will be constrained by val[X], which is never greater than val[W].
Thus the current estimate of val[W] is optimum and hence algorithm computes the correct values for all the vertices.
You could also use the "binary search on the answer" paradigm. That is, do a binary search on the weights, testing for each weight w whether you can find a path in the graph using only edges of weight greater than w.
The largest w for which you can (found through binary search) gives the answer. Note that you only need to check if a path exists, so just an O(|E|) breadth-first/depth-first search, not a shortest-path. So it's O(|E|*log(max W)) in all, comparable to the Dijkstra/Kruskal/Prim's O(|E|log |V|) (and I can't immediately see a proof of those, too).
Use either Prim's or Kruskal's algorithm. Just modify them so they stop when they find out that the vertices you ask about are connected.
EDIT: You ask for maximum minimum, but your example looks like you want minimum maximum. In case of maximum minimum Kruskal's algorithm won't work.
EDIT: The example is okay, my mistake. Only Prim's algorithm will work then.
I am not sure that Prim will work here. Take this counterexample:
V = {1, 2, 3, 4}
E = {(1, 2), (2, 3), (1, 4), (4, 2)}
weight function w:
w((1,2)) = .1,
w((2,3)) = .3
w((1,4)) = .2
w((4,2)) = .25
If you apply Prim to find the maxmin path from 1 to 3, starting from 1 will select the 1 --> 2 --> 3 path, while the max-min distance is attained for the path that goes through 4.
This can be solved using a BFS style algorithm, however you need two variations:
Instead of marking each node as "visited", you mark it with the minimum weight along the path you took to reach it.
For example, if I and J are neighbors, I has value w1, and the weight of the edge between them is w2, then J=min(w1, w2).
If you reach a marked node with value w1, you might need to remark and process it again, if assigning a new value w2 (and w2>w1). This is required to make sure you get the maximum of all minimums.
For example, if I and J are neighbors, I has value w1, J has value w2, and the weight of the edge between them is w3, then if min(w2, w3) > w1 you must remark J and process all it's neighbors again.
Ok, answering my own question here just to try and get a bit of feedback I had on the tentative solution I worked out before posting here:
Each node stores a "path fragment", this is the entire path to itself so far.
0) set current vertex to the starting vertex
1) Generate all path fragments from this vertex and add them to a priority queue
2) Take the fragment off the top off the priority queue, and set the current vertex to the ending vertex of that path
3) If the current vertex is the target vertex, then return the path
4) goto 1
I'm not sure this will find the best path though, I think the exit condition in step three is a little ambitious. I can't think of a better exit condition though, since this algorithm doesn't close vertices (a vertex can be referenced in as many path fragments as it likes) you can't just wait until all vertices are closed (like Dijkstra's for example)
You can still use Dijkstra's!
Instead of using +, use the min() operator.
In addition, you'll want to orient the heap/priority_queue so that the biggest things are on top.
Something like this should work: (i've probably missed some implementation details)
let pq = priority queue of <node, minimum edge>, sorted by min. edge descending
push (start, infinity) on queue
mark start as visited
while !queue.empty:
current = pq.top()
pq.pop()
for all neighbors of current.node:
if neighbor has not been visited
pq.decrease_key(neighbor, min(current.weight, edge.weight))
It is guaranteed that whenever you get to a node you followed an optimal path (since you find all possibilities in decreasing order, and you can never improve your path by adding an edge)
The time bounds are the same as Dijkstra's - O(Vlog(E)).
EDIT: oh wait, this is basically what you posted. LOL.