Permutations without recursive function call

前端 未结 8 1016
北荒
北荒 2020-11-27 04:15

Requirement: Algorithm to generate all possible combinations of a set , without duplicates , or recursively calling function to return results.

The majority , if not

相关标签:
8条回答
  • 2020-11-27 04:55

    Here could be another solution, inspired from the Steinhaus-Johnson-Trotter algorithm:

    function p(input) {
      var i, j, k, temp, base, current, outputs = [[input[0]]];
      for (i = 1; i < input.length; i++) {
        current = [];
        for (j = 0; j < outputs.length; j++) {
          base = outputs[j];
          for (k = 0; k <= base.length; k++) {
            temp = base.slice();
            temp.splice(k, 0, input[i]);
            current.push(temp);
          }
        }
        outputs = current;
      }
      return outputs;
    }
    
    // call
    
    var outputs = p(["a", "b", "c", "d"]);
    for (var i = 0; i < outputs.length; i++) {
      document.write(JSON.stringify(outputs[i]) + "<br />");
    }

    0 讨论(0)
  • 2020-11-27 04:55

    Here's a snippet for an approach that I came up with on my own, but naturally was also able to find it described elsewhere:

    generatePermutations = function(arr) {
      if (arr.length < 2) {
        return arr.slice();
      }
      var factorial = [1];
      for (var i = 1; i <= arr.length; i++) {
        factorial.push(factorial[factorial.length - 1] * i);
      }
    
      var allPerms = [];
      for (var permNumber = 0; permNumber < factorial[factorial.length - 1]; permNumber++) {
        var unused = arr.slice();
        var nextPerm = [];
        while (unused.length) {
          var nextIndex = Math.floor((permNumber % factorial[unused.length]) / factorial[unused.length - 1]);
          nextPerm.push(unused[nextIndex]);
          unused.splice(nextIndex, 1);
        }
        allPerms.push(nextPerm);
      }
      return allPerms;
    };
    Enter comma-separated string (e.g. a,b,c):
    <br/>
    <input id="arrInput" type="text" />
    <br/>
    <button onclick="perms.innerHTML = generatePermutations(arrInput.value.split(',')).join('<br/>')">
      Generate permutations
    </button>
    <br/>
    <div id="perms"></div>

    Explanation

    Since there are factorial(arr.length) permutations for a given array arr, each number between 0 and factorial(arr.length)-1 encodes a particular permutation. To unencode a permutation number, repeatedly remove elements from arr until there are no elements left. The exact index of which element to remove is given by the formula (permNumber % factorial(arr.length)) / factorial(arr.length-1). Other formulas could be used to determine the index to remove, as long as it preserves the one-to-one mapping between number and permutation.

    Example

    The following is how all permutations would be generated for the array (a,b,c,d):

    #    Perm      1st El        2nd El      3rd El    4th El
    0    abcd   (a,b,c,d)[0]   (b,c,d)[0]   (c,d)[0]   (d)[0]
    1    abdc   (a,b,c,d)[0]   (b,c,d)[0]   (c,d)[1]   (c)[0]
    2    acbd   (a,b,c,d)[0]   (b,c,d)[1]   (b,d)[0]   (d)[0]
    3    acdb   (a,b,c,d)[0]   (b,c,d)[1]   (b,d)[1]   (b)[0]
    4    adbc   (a,b,c,d)[0]   (b,c,d)[2]   (b,c)[0]   (c)[0]
    5    adcb   (a,b,c,d)[0]   (b,c,d)[2]   (b,c)[1]   (b)[0]
    6    bacd   (a,b,c,d)[1]   (a,c,d)[0]   (c,d)[0]   (d)[0]
    7    badc   (a,b,c,d)[1]   (a,c,d)[0]   (c,d)[1]   (c)[0]
    8    bcad   (a,b,c,d)[1]   (a,c,d)[1]   (a,d)[0]   (d)[0]
    9    bcda   (a,b,c,d)[1]   (a,c,d)[1]   (a,d)[1]   (a)[0]
    10   bdac   (a,b,c,d)[1]   (a,c,d)[2]   (a,c)[0]   (c)[0]
    11   bdca   (a,b,c,d)[1]   (a,c,d)[2]   (a,c)[1]   (a)[0]
    12   cabd   (a,b,c,d)[2]   (a,b,d)[0]   (b,d)[0]   (d)[0]
    13   cadb   (a,b,c,d)[2]   (a,b,d)[0]   (b,d)[1]   (b)[0]
    14   cbad   (a,b,c,d)[2]   (a,b,d)[1]   (a,d)[0]   (d)[0]
    15   cbda   (a,b,c,d)[2]   (a,b,d)[1]   (a,d)[1]   (a)[0]
    16   cdab   (a,b,c,d)[2]   (a,b,d)[2]   (a,b)[0]   (b)[0]
    17   cdba   (a,b,c,d)[2]   (a,b,d)[2]   (a,b)[1]   (a)[0]
    18   dabc   (a,b,c,d)[3]   (a,b,c)[0]   (b,c)[0]   (c)[0]
    19   dacb   (a,b,c,d)[3]   (a,b,c)[0]   (b,c)[1]   (b)[0]
    20   dbac   (a,b,c,d)[3]   (a,b,c)[1]   (a,c)[0]   (c)[0]
    21   dbca   (a,b,c,d)[3]   (a,b,c)[1]   (a,c)[1]   (a)[0]
    22   dcab   (a,b,c,d)[3]   (a,b,c)[2]   (a,b)[0]   (b)[0]
    23   dcba   (a,b,c,d)[3]   (a,b,c)[2]   (a,b)[1]   (a)[0]
    

    Note that each permutation # is of the form:

    (firstElIndex * 3!) + (secondElIndex * 2!) + (thirdElIndex * 1!) + (fourthElIndex * 0!)
    

    which is basically the reverse process of the formula given in the explanation.

    0 讨论(0)
  • 2020-11-27 05:01

    Here is an answer from @le_m. It might be of help.

    The following very efficient algorithm uses Heap's method to generate all permutations of N elements with runtime complexity in O(N!):

    function permute(permutation) {
      var length = permutation.length,
          result = [permutation.slice()],
          c = new Array(length).fill(0),
          i = 1, k, p;
    
      while (i < length) {
        if (c[i] < i) {
          k = i % 2 && c[i];
          p = permutation[i];
          permutation[i] = permutation[k];
          permutation[k] = p;
          ++c[i];
          i = 1;
          result.push(permutation.slice());
        } else {
          c[i] = 0;
          ++i;
        }
      }
      return result;
    }
    
    console.log(JSON.stringify(permute([1, 2, 3, 4])));

    0 讨论(0)
  • 2020-11-27 05:02

    A fairly simple C++ code without recursion.

    #include <vector>
    #include <algorithm>
    #include <iterator>
    #include <iostream>
    #include <string>
    
    // Integer data
    void print_all_permutations(std::vector<int> &data) {
        std::stable_sort(std::begin(data), std::end(data));
        do {
            std::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " ")), std::cout << '\n';
        } while (std::next_permutation(std::begin(data), std::end(data)));
    }
    
    // Character data (string)
    void print_all_permutations(std::string &data) {
        std::stable_sort(std::begin(data), std::end(data));
        do {
            std::copy(data.begin(), data.end(), std::ostream_iterator<char>(std::cout, " ")), std::cout << '\n';
        } while (std::next_permutation(std::begin(data), std::end(data)));
    }
    
    int main()
    {
        std::vector<int> v({1,2,3,4});
        print_all_permutations(v);
    
        std::string s("abcd");
        print_all_permutations(s);
    
        return 0;
    }
    

    We can find next permutation of a sequence in linear time.

    0 讨论(0)
  • 2020-11-27 05:03

    Here is a simple solution to compute the nth permutation of a string:

    function string_nth_permutation(str, n) {
        var len = str.length, i, f, res;
    
        for (f = i = 1; i <= len; i++)
            f *= i;
    
        if (n >= 0 && n < f) {
            for (res = ""; len > 0; len--) {
                f /= len;
                i = Math.floor(n / f);
                n %= f;
                res += str.charAt(i);
                str = str.substring(0, i) + str.substring(i + 1);
            }
        }
        return res;
    }
    

    The algorithm follows these simple steps:

    • first compute f = len!, there are factorial(len) total permutations of a set of len different elements.
    • as the first element, divide the permutation number by (len-1)! and chose the element at the resulting offset. There are (len-1)! different permutations that have any given element as their first element.
    • remove the chosen element from the set and use the remainder of the division as the permutation number to keep going.
    • perform these steps with the rest of the set, whose length is reduced by one.

    This algorithm is very simple and has interesting properties:

    • It computes the n-th permutation directly.
    • If the set is ordered, the permutations are generated in lexicographical order.
    • It works even if set elements cannot be compared to one another, such as objects, arrays, functions...
    • Permutation number 0 is the set in the order given.
    • Permutation number factorial(a.length)-1 is the last one: the set a in reverse order.
    • Permutations outside this range are returned as undefined.

    It can easily be converted to handle a set stored as an array:

    function array_nth_permutation(a, n) {
        var b = a.slice();  // copy of the set
        var len = a.length; // length of the set
        var res;            // return value, undefined
        var i, f;
    
        // compute f = factorial(len)
        for (f = i = 1; i <= len; i++)
            f *= i;
    
        // if the permutation number is within range
        if (n >= 0 && n < f) {
            // start with the empty set, loop for len elements
            for (res = []; len > 0; len--) {
                // determine the next element:
                // there are f/len subsets for each possible element,
                f /= len;
                // a simple division gives the leading element index
                i = Math.floor(n / f);
                // alternately: i = (n - n % f) / f;
                res.push(b.splice(i, 1)[0]);
                // reduce n for the remaining subset:
                // compute the remainder of the above division
                n %= f;
                // extract the i-th element from b and push it at the end of res
            }
        }
        // return the permutated set or undefined if n is out of range
        return res;
    }
    

    clarification:

    • f is first computed as factorial(len).
    • For each step, f is divided by len, giving exacty the previous factorial.
    • n divided by this new value of f gives the slot number among the len slots that have the same initial element. Javascript does not have integral division, we could use (n / f) ... 0) to convert the result of the division to its integral part but it introduces a limitation to sets of 12 elements. Math.floor(n / f) allows for sets of up to 18 elements. We could also use (n - n % f) / f, probably more efficient too.
    • n must be reduced to the permutation number within this slot, that is the remainder of the division n / f.

    We could use i differently in the second loop, storing the division remainder, avoiding Math.floor() and the extra % operator. Here is an alternative for this loop that may be even less readable:

            // start with the empty set, loop for len elements
            for (res = []; len > 0; len--) {
                i = n % (f /= len);
                res.push(b.splice((n - i) / f, 1)[0]);
                n = i;
            }
    
    0 讨论(0)
  • 2020-11-27 05:03

    I dare to add another answer, aiming at answering you question regarding slice, concat, reverse.

    The answer is it is possible (almost), but it would not be quite effective. What you are doing in your algorithm is the following:

    • Find the first inversion in the permutation array, right-to-left (inversion in this case defined as i and j where i < j and perm[i] > perm[j], indices given left-to-right)
    • place the bigger number of the inversion
    • concatenate the processed numbers in reversed order, which will be the same as sorted order, as no inversions were observed.
    • concatenate the second number of the inversion (still sorted in accordsnce with the previos number, as no inversions were observed)

    This is mainly, what my first answer does, but in a bit more optimal manner.

    Example

    Consider the permutation 9,10, 11, 8, 7, 6, 5, 4 ,3,2,1 The first inversion right-to-left is 10, 11. And really the next permutation is: 9,11,1,2,3,4,5,6,7,8,9,10=9concat(11)concat(rev(8,7,6,5,4,3,2,1))concat(10)

    Source code Here I include the source code as I envision it:

    var nextPermutation = function(arr) {
      for (var i = arr.length - 2; i >= 0; i--) {
         if (arr[i] < arr[i + 1]) {
            return arr.slice(0, i).concat([arr[i + 1]]).concat(arr.slice(i + 2).reverse()).concat([arr[i]]);
         }
      }
      // return again the first permutation if calling next permutation on last.
      return arr.reverse();
    }
    
    console.log(nextPermutation([9, 10, 11, 8, 7, 6, 5, 4, 3, 2, 1]));
    console.log(nextPermutation([6, 5, 4, 3, 2, 1]));
    console.log(nextPermutation([1, 2, 3, 4, 5, 6]));
    

    The code is avaiable for jsfiddle here.

    0 讨论(0)
提交回复
热议问题