views:

1752

answers:

5

I have a resource scheduling issue in Java where things need to be sequenced, but there are restrictions on what resources can be next to each other. A good analogy is a string of "digits", where only certain digits can be next to each other. My solution was recursive, and works fine for small strings, but run time is O(X^N), where X is the number of possible digits (the base), and N is the length of the string. It quickly becomes unmanageable.

Using the compatibility matrix below, here are a few examples of allowed strings
Length of 1: 0, 1, 2, 3, 4
Length of 2: 02, 03, 14, 20, 30, 41
Length of 3: 020, 030, 141, 202, 203, 302, 303, 414

     0  1  2  3  4
   ---------------------
0|  0  0  1  1  0
1|  0  0  0  0  1
2|  1  0  0  0  0
3|  1  0  0  0  0
4|  0  1  0  0  0

My solution for counting all strings of length N was start with an empty string, permute the first digit, and make a recursive call for all strings of length N-1. The recursive calls check the last digit that was added and try all permutations that can be next to that digit. There are some optimizations made so that I don't try and permute 00, 01, 04 every time, for example - only 02, 03, but performance is still poor as it scales from base 5 (the example) to base 4000.

Any thoughts on a better way to count the permutations other than trying to enumerate all of them?

+1  A: 

Maybe I don't understand this, but wouldn't this be served by having a table of lists that for each digit has a list of valid digits that could follow it.

Then your routine to generate will take an accumulated result, the digit number, and the current digit. Something like:

// not really Java - and you probably don't want chars, but you'll fix it
void GenerateDigits(char[] result, int currIndex, char currDigit)
{
    if (currIndex == kMaxIndex) {
        NotifyComplete(result);
        return;
    }
    char[] validFollows = GetValidFollows(currDigit); // table lookup
    foreach (char c in validFollows) {
        result[currIndex] = c;
        GenerateDigits(result, currIndex+1, c);
    }
}

The complexity increases as a function of the number of digits to generate, but that function depends on the total number of valid follows for any one digit. If the total number of follows is the same for every digit, let's say, k, then the time to generate all possible permutations is going to be O(k^n) where n is the number of digits. Sorry, I can't change math. The time to generate n digits in base 10 is 10^n.

plinth
Thanks. Your suggestion has already been implemented as the optimization that I mentioned where I only look at the digits that can follow the current digit. That improved runtime from O(n^2), but the exponential scale still falls apart when we get to thousands of digits.
I don't think he made a suggestion. He was trying to understand your algorithm. You use too much the words "linear", "exponential", "O(N^2)", without understanding what they mean.
+3  A: 

Do you just want to know how many strings of a given length you can build with the rules in the given matrix? If so, that an approach like this should work:

n = 5
maxlen = 100

combine = [
      [0, 0, 1, 1, 0],
      [0, 0, 0, 0, 1],
      [1, 0, 0, 0, 0],
      [1, 0, 0, 0, 0],
      [0, 1, 0, 0, 0]
   ]

# counts of strings starting with 0,1,...,4, initially for strings of length one:
counts = [1, 1, 1, 1, 1]

for size in range(2, maxlen+1):
   # calculate counts for size from count for (size-1)
   newcount = []
   for next in range(n):
      total = 0
      for head in range(n):
         if combine[next][head]:
            # |next| can be before |head|, so add the counts for |head|
            total += counts[head]
      # append, so that newcount[next] == total
      newcount.append(total)
   counts = newcount
   print "length %i: %i items" % (size, sum(counts))
sth
Thanks. I think this would work, but I believe this has the same issue I have... nested loops/recursions over N elements. Please correct me if I'm missing something.
You're missing something. It's the same dynamic programming algorithms as suggested by unknown (yahoo).
It's O(n^2 * maxlen), and that's not great. From your description I thought your algorithm might be something like O(n^2 * n^(maxlen-1)), but I'm not really sure how you do it. I think the matrix solution above could be implemented in O(n^2*log(maxlen)), but that also doesn't really help either...
sth
But then again: If you have a n*n matrix with the compatibility information and need to look at all the elements in it, you need O(n^2) time to do that. Is there maybe a better way to describe the compatibilities?
sth
+2  A: 

Your algorithm seems to be optimal.

How are you using these permutations? Are you accumulating them in one list, or using it one by one? Since there are a huge number of such permutations, so the poor performance maybe due to large memory usage (if you are collecting all of them) or it just takes so much time. You just can't do billions of loops in trivial time.

Reply to comment:

If you just want to count them, then you can using dynamic programming:

Let count[n][m] be an array, where count[l][j] is the number of such permutations whose length is l and end with j,

then count[l][i] = count[l-1][i1]+count[l-1][i2]+..., where i1, i2, ... are the digits that can precede i (this can be saved in an pre-calculated array).

Every cell of count can be filled by summing K numbers (K depends on the compatible matrix), so the complexity is O(KMN), M is the length of the permutation, and N is the total number of digits.

Just counting them for now - not using them. As far as an iterative/recursive solution goes, I think mine is pretty good, but as you say, you can't do 4000^10 of those in reasonable time, so I'm trying to find an alternate.
+1  A: 

I'm not exactly sure what you're asking, but since there are potentially n! permutations of a string of n digits, you're not going to be able to list them faster than n!. I'm not exactly sure how you think you got a runtime of O(n^2).

Brian Postow
sorry - typo/brain lapse. O(N^N)
O(K^N), where K is the number of distinct digits and N is the length of the "permutation".
+13  A: 
MizardX
Using power of compatible matrix is a very clean idea. But one should note that the formula is only correct for this case.
Also, the answer should mention why this is correct. The correct explanation is that A^i[x][y] is the number of "compatible" strings of length i that start with x and end with y. You can clearly see why multiplication preserves this invariant.
This is cool, but wasn't the question about the size of a restricted set of *permutations*? This doesn't enforce the constraint that a digit may be used at most once, which is what I take 'permutation' to mean. Oh, wait, '202' is an example. Just a confusing expression of the question, then.
Darius Bacon
Very good solution - thanks. I have it working as suggested, and now trying to optimize it using the exponentiation by squaring method mentioned by stefan.