[ Team LiB ] Previous Section Next Section

7.6 Recursion and Stacks

The techniques for converting recursive method calls to iterative ones are suitable only for methods that take a single search path at every decision node when navigating through the solution space. For more complex recursive methods that evaluate multiple paths from some nodes, you can convert a recursive method into an iterative method based on a stack. This is best illustrated with an example. I'll use here the problem of looking for all the files with names ending in some particular string.

The following method runs a recursive search of the filesystem, printing all nondirectory files that end in a particular string:

public static String FS = System.getProperty("file.separator");
public static void filesearch1(String root, String fileEnding)
{
  File f = new File(root);
  String[  ] filelist = f.list(  );
  if (filelist =  = null)
    return;
  for (int i = filelist.length-1; i >= 0; i--)
  {
    f = new File(root, filelist[i]);
    if (f.isDirectory(  ))
      filesearch1(root+FS+filelist[i], fileEnding);
    else if(filelist[i].toUpperCase(  ).endsWith(fileEnding))
      System.out.println(root+ls+filelist[i]);
  }
}

To convert this into an iterative search, it is not sufficient to use an extra variable to hold the current directory. At any one directory, there are several possible directories underneath, all of which must be held onto and searched, and you cannot reference them all from a plain variable. Instead, you can make that variable into a collection object. The standard object to use is a stack. With this hint in mind, the method converts quite easily:

public static void filesearch2(String root, String fileEnding)
{
  Stack dirs = new Stack(  );
  dirs.push(root);
  File f;
  int i;
  String[  ] filelist;
  while(!dirs.empty(  ))
  {
    f = new File(root = (String) dirs.pop(  ));
    filelist = f.list(  );
    if (filelist =  = null)
      continue;
    for (i = filelist.length-1; i >= 0; i--)
    {
      f = new File(root, filelist[i]);
      if (f.isDirectory(  ))
        dirs.push(root+FS+filelist[i]);
      else if(filelist[i].toUpperCase(  ).endsWith(fileEnding))
        System.out.println(root+ls+filelist[i]);
    }
  }
}

In fact, the structures of the two methods are almost the same. This second iterative version has the main part of the body wrapped in an extra loop that terminates when the extra variable holding the stack becomes empty. Otherwise, instead of the recursive call, the directory is added to the stack.

In the cases of these particular search methods, the time-measurement comparison shows that the iterative method actually takes 5% longer than the recursive method. This is due to the iterative method having the overhead of the extra stack object to manipulate, whereas filesystems are generally not particularly deep (the ones I tested on were not), so the recursive algorithm is not particularly inefficient. This illustrates that a recursive method is not always worse than an iterative one.

Note that the methods here were chosen for illustration, using an easily understood problem that could be managed iteratively and recursively. Since the I/O is actually the limiting factor for these methods, there would not be much point in actually making the optimization shown.

For this example, I eliminated the I/O overhead, as it would have swamped the times and made it difficult to determine the difference between the two implementations. To do this, I mapped the filesystem into memory using a simple replacement of the java.io.File class. This stored a snapshot of the filesystem in a hash table. (Actually, only the full pathnames of directories as keys, and their associated string array list of files as values, need be stored.)

This kind of trick—replacing classes with another implementation to eliminate extraneous overhead—is quite useful when you need to identify exactly where times are going.

    Previous Section Next Section