13

Consider following code.

public class Permutations {
    static int count=0;
    static void permutations(String str, String prefix){
        if(str.length()==0){
            System.out.println(prefix);
        }
        else{
            for(int i=0;i<str.length();i++){
                count++;
                String rem = str.substring(0,i) + str.substring(i+1);
                permutations(rem, prefix+str.charAt(i));
            }
        }

    }
    public static void main(String[] args) {
        permutations("abc", "");
        System.out.println(count);
    }

}

here the logic, that i think is followed is- it considers each character of the string as a possible prefix and permutes the remaining n-1 characters.
so by this logic recurrence relation comes out to be

T(n) = n( c1 + T(n-1) )          // ignoring the print time

which is obviously O(n!). but when i used a count variable to see wheather algo really grows in order of n!, i found different results.
for 2-length string for count++(inside for loop) runs 4 times, for 3-length string value of count comes 15 and for 4 and 5-length string its 64 and 325.
It means it grows worse than n!. then why its said that this(and similar algos which generate permuatations) are O(n!) in terms of run time.

Holt
  • 36,600
  • 7
  • 92
  • 139
  • why? Can you explain a bit? –  Aug 24 '16 at 14:09
  • `n!` is the number of permutations, if you increment `count` inside the first block of the `if`, you will get `n!`, but what you are actually counting is the number of calls to `permutations` which is larger than `n!`. – Holt Aug 24 '16 at 14:15
  • @Holt but these calls are also responsible of increasing run time of the algorithm. Why shouldn't we consider this. I know there can only be n! permutations and if we increment count in if block it will surely print n!. –  Aug 24 '16 at 14:19
  • @JavaGeek This is how big-O notation works - Theoretical complexity is not the same as runtime complexity. – Holt Aug 24 '16 at 14:22
  • @SauravSahu how this question is possible dublicate of the one you mentioned? I am asking if there are more than n! calls then why we say run time as n! only? –  Aug 24 '16 at 14:22
  • 1
    @Holt you mean though it looks greather than n! but it certainly a constant multiple of n!. thats why i am getting heigher values for count... Can you please base your statement on some fact/proof that the value of count here is certainly a constant multiple of n! and doesnt depend on n –  Aug 24 '16 at 14:25
  • For simplicity. It might be that the actual complexity is `O(n! + n*n)`, but this is a subset of `O(n!)`. – chill Aug 24 '16 at 14:29
  • @JavaGeek See my answer. – Holt Aug 24 '16 at 14:32
  • 1
    Holt's answer is correct, but in actual fact the permutations function takes O(N * N!) time, or O((N+1)!) time, because it takes O(N) time to print each string. – Matt Timmermans Aug 24 '16 at 16:44
  • It also takes O(N) time to do `prefix+str.charAt(i)` – Matt Timmermans Aug 24 '16 at 16:49

2 Answers2

24

People say this algorithm is O(n!) because there are n! permutations, but what you are counting here are (in a sense) function calls - And there are more function calls than n!:

  • When str.length() == n, you do n calls;
  • For each of these n calls with str.length() == n - 1, you do n - 1 calls;
  • For each of these n * (n - 1) calls with str.length() == n - 2 you do n - 2 calls;
  • ...

You do n! / k! calls with an input str of length k1, and since the length goes from n to 0, the total number of calls is:

sum k = 0 ... n (n! / k!) = n! sum k = 0 ... n (1 / k!)

But as you may know:

sum k = 0 ... +oo 1 / k! = e1 = e

So basically, this sum is always less than the constant e (and greater than 1), so you can say that the number of calls is O(e.n!) which is O(n!).

Runtime complexity is often different from theoretical complexity. In theoretical complexity, people want to know the number of permutations because the algorithm is probably going to check each of these permutations (so there are effectively n! check done), but in reality there is much more thing going on.

1 This formula will actually give you one compared to the values you got since you did not account for the initial function call.

Holt
  • 36,600
  • 7
  • 92
  • 139
  • Thanks! really a great explanation. –  Aug 24 '16 at 14:38
  • Shouldn't it be n!/(k-1)! when str.length() == k because when the string is length n we can spawn n calls? n!/(n-1)! = (n * n-1 * n-2) / (n-1 * n-2 * n-3) = n calls – Benyam Ephrem Sep 06 '19 at 14:31
  • @BenyamEphrem When I say *"You do `n!/k!` calls with `str.length() == k`"*, I mean that there are `n!/k!` calls of the function with `str.length() == k`, not that you are doing `n!/k!` calls within the call where `str.length() == k`. And you will only call the method once with a string of length `n` (the initial call). I admit that this is not that clear, took me some time to get back the original meaning of my own answer... – Holt Sep 06 '19 at 15:10
  • Ah, crystal clear, so at level 0 (the first call) k = n so -> n!/(n)! = 1 call. At level 1, k = n - 1 so -> n!/(n-1)! = (n * n-1 * n-2) / (n-1 * n-2 * n-3) = n calls, etc. Thanks! – Benyam Ephrem Sep 06 '19 at 20:11
1

this answer is for people like me who doesn't remember e=1/0!+1/1!+1/2!+1/3!...

I can explain using a simple example, say we want all the permutation of "abc"

        /    /   \     <--- for first position, there are 3 choices
       /\   /\   /\    <--- for second position, there are 2 choices
      /  \ /  \ /  \   <--- for third position, there is only 1 choice

above is the recursion tree, and we know that there are 3! leaf nodes, which represents all possible permutations of "abc" (which is also where we perform an action on the result, ie print()), but since you are counting all function calls, we need to know how many tree nodes in total (leaf + internal)

if it was a complete binary tree, we know there are 2^n leaf nodes...how many internal nodes?

x = |__________leaf_____________|------------------------|  
let this represent 2^n leaf nodes, |----| represents the max number of
nodes in the level above, since each node has 1 parent, 2nd last level
cannot have more nodes than leaf
since its binary, we know second last level = (1/2)leaf 
x = |__________leaf_____________|____2nd_____|-----------|
same for the third last level...which is (1/2)sec
x = |__________leaf_____________|____2nd_____|__3rd_|----|

x can be used to represent total number of tree nodes, and since we are always cutting half on the initial |-----| we know that total <= 2*leaf

now for permutation tree

x = |____leaf____|------------|
let this represent n! leaf nodes
since its second last level has 1 branch, we know second last level = x 
x = |____leaf____|____2nd_____|-------------|
but third last level has 2 branches for each node, thus = (1/2)second
x = |____leaf____|____2nd_____|_3rd_|-------|
fourth last level has 3 branches for each node, thus = (1/3)third
x = |____leaf____|____2nd_____|_3rd_|_4|--| |
| | means we will no longer consider it

here we see that total < 3*leaf, this is as expected (e = 2.718)

watashiSHUN
  • 9,684
  • 4
  • 36
  • 44