Time Complexity of this Word Chain

Viewed 425

I have written a bigger program, I am constructing a graph Graph(V,E), from words in a data file. Then I am parsing another file with words on same line: "other there" <-- like that, the first string is start word, the second is end word, as seen below:

while (true) {
    String line = reader.readLine();
    if (line == null) { break; }
    assert line.length() == 11; 
    String start = line.substring(0, 5);
    String goal = line.substring(6, 11);
    System.out.println(
          G.findShortestPath(
                words.indexOf(start),
                   words.indexOf(goal)));   
}
    

I am using undirected graphs with BFS. I am doing word transformation word chain, like this: climb → blimp → limps → pismo → moist → stoic

The input file contains words of length five, and a path/connection is defined as such so it goes from Xto Y in one step, only if, and only if the four last letters in Xare found in Y.

What is known, and I have calculated.

Time complexity: O(V + E) for building the graph G(V, E) of words. The second part of the program consists of a while loop and a for loop of finding the shortest path (using BFS), which is O(V^2).

Space complexity: O(V) in the worst case. The graph holds all the words. The nodes are made up of a single node class object which contains n neighbor(s).

Process:

  1. Program loads into buffer a file with words.
  2. The program builds the graph.
  3. The program runs test and loads into buffer information from a test file (different file). Then selects start and end node and performs the shortest path search.

If there's a connection, the code returns the shortest path length. If there's no connection between two words or end/goal cannot be reached, we return -1.

Now, I am trying to come up with an O(?) time algorithm or total time complexity for V,E,F, where:

  • V is the number of vertices
  • E is the number of edges
  • F is the number of test cases (number of lines in the test file)*

*Number of test cases in function: public void performTest(String filename) throws IOException. The body of such function is here, see above. Now, I know, that for lines n, there will be same n amount of test cases. F=O(n). But, in what way, can one incorporate or add this calculation to make a general O expression with variables that holds for whatever amount of words in list and words in graph and words from testfile.


The main body of the BFS algorithm is the nesting of two loops, the while loop visits each vertex once, so it is O(|V|), and the for nested in the while , Since each edge will be checked only once when its starting vertex u is dequeued, and each vertex will be dequeued at most once, so the edge will be checked at most once, and the total is 0 (|E|).The time complexity of BFS is 0 (|V|+|E|)


Contents in Testfile: The first word becomes the starting word, the second on the same line becomes the end or target word.

blimp moist
limps limps

Some other code, I've written previously:

public static void main(String... args) throws IOException {
  ArrayList<String> words = new ArrayList<String>();
  words.add("their");
  words.add("moist");
  words.add("other");
  words.add("blimp");

  Graph g = new Graph(words.size());
  for(String word: words) {
    for(String word2: words){
      g.addEdge(words.indexOf(word), words.indexOf(word2));
    }
  }

  BufferedReader readValues = null;

  try {

    readValues =
        new BufferedReader(new InputStreamReader(new FileInputStream("testfile.txt")));

    String line = null;
    while ((line = readValues.readLine()) != null) {
      // assert line.length() == 11;
      String[] tokens = line.split(" ");
      String start = tokens[0];
      String goal = tokens[1];

      BreadthFirstPaths bfs = new BreadthFirstPaths(g, words.indexOf(start));
      if (bfs.hasPathTo(words.indexOf(goal))) {
        System.out.println("Shortest path distance from " + start + " to " + goal + " = " + bfs.distTo(words.indexOf(goal)));
      } else System.out.println("Nothing");
    }

  } finally {
    if (readValues != null) {
      try {
        readValues.close();
      } catch (Throwable t) {
        t.printStackTrace();
      }
    }
  }

Notice: Not interested in FASTER solutions.

3 Answers

The straightforward answer:

The direct approach would be to use Floyd - Warshall algorithm. This algorithm computes shortest paths between all pairs of vertices in a directed graph without negative cycles. Since you are using an undirected graph with positive weights it is sufficient to replace every undirected edge (u,v) with directed pair (u,v), (v,u).

The runtime of Floyd - Warshall is O(V ^ 3) and it would compute all the answers you could ever seek at once, given that you can retrieve them in a reasonable time. (Which should be rather easy since you already have V^3 of a breathing room).

Getting faster:

In your case that most likely isn't optimal(Not to mention that I don't know how many queries will you make - if only a few, then FW is definitely an overkill). Since your graph doesn't have any negative edges and it seems that the edge count is only C * |V| from your space complexity we can go further. Enters Djikstra.
Djikstra algoritm's complexity is O(E + Vlog(V)).
Considering that you most likely have only ~ C * V edges, this would bring the repeated Djikstra's computation costs to F * O(V * log(V)).

And faster:

If you wish to give frying your brain a go, Djikstra can be improved upon in some special cases by using the dark magic of the fibonacci heaps(which are modified for the purpose of the algorithm to make things more confusing). From what I can see, your case could be special enough so that the O(N * sqrt(log(N))) from this article is achievable.
Their assumption are:

  • n vertices
  • m edges
  • the the longest arc(a length of an edge if my google-fu is correct) being bounded by a polynomial function of n.

This is it for my attempt at a quick dive into the shortest path problem. If you wish to research more, I would recommend looking into the all-pairs-shotest-paths problem in general. There are many other algorithms that are similar in the complexity. Your ideal approach will also depend on your F.

P.S.:

Unless you have many, many words, your neighbor count can still be rather big: 5! * 26 in the worst case to be precise. (Four letters are fixed and one is arbitrary - possible permutations * letter count). Can be lower in case of repetitions, still it isn't small, although it can technically be considered a constant.

It seems to me that you are simply asking about the computational complexity of your existing solution, expressed in terms of the 3 variables V, E and F.

If I am reading your question correctly, it is

  O(V + E)              // loading
+ O(V^2) done F times   // F test cases

which simplifies to:

O(V + E + (F * V^2)) 

This assumes that your Big-O characterizations of the load and search times are correct. We cannot confirm1 that those characterizations are correct without seeing the complete Java source code for your solution. (An English or pseudo-code description is too imprecise.)

Note that the above formula is "canonical". It cannot be simplified further unless you eliminate variables; e.g. by treating them as a constant or placing bounds on them.

However, if we can assume that F > 0, we can reduce it to:

O(E + (F * V^2))

since when F > 0 the F*V^2 term will dominate the V term as either F or V tends to infinity. (Intuitively, the F == 0 case corresponds to just loading the graph and running no test cases. You would expect the performance characteristics to be different in that case.)

It is possible that the E variable could be approximated as function of V. (Each edge from one node to another represents a permutation of a word with one letter changed. If one did some statistical analysis of words in the English language, it may be possible to determine a the average number of edges per node as a function of the number of nodes.) If we can prove that this (hypothetical) average_E(V) function is O(V^2) or better (as seems likely!), then we can eliminate the E term from the overall complexity class.

(Of course, that assumes that the input graph contains no duplicate or incorrect edges per the description of the problem you are trying to solve.)

Finally, it seems that the O(V^2) is actually a measure of worst-case performance, depending on the sparseness of the graph. So you need to factor that into your answer ... and the way that you attempt to validate the complexity.


1 - The O(V^2) seems a bit suspicious to me.

Time: O(F * (V + E))

Space: O(V + E)

Following what you described

  • V: Vertices
  • E: Edges
  • F: path queries

The BFS algorithm complexity is O(V + E) time, and O(V) space

Each query is a BFS without any modification, so the time complexity is O(F * (V + E)), and space complexity is the same as one single BFS since you are using the same structure O(V), but we have to consider the space used to store the graph O(E).

Graph construction, you are iterating over all pairs of words and adding one edge for each word your graph always have $E = V^2$. If you had asked advice to improve your algorithm as is usual in this community, I would tell you to avoid adding edges that should not be used (those with more than 1 character difference).

Related