没有递归函数调用的排列组合

33

需求:编写一个算法,生成一个集合的所有可能组合,不包含重复项,或者通过递归调用函数返回结果。

大多数情况下,如果不是全部,Permutations in JavaScript?提供的答案会在循环或其他函数中递归调用函数来返回结果。

循环内递归函数调用的示例

function p(a, b, res) {
  var b = b || [], res = res || [], len = a.length;
  if (!len) 
    res.push(b)
  else 
    for (var i = 0; i < len 
         // recursive call to `p` here
       ; p(a.slice(0, i).concat(a.slice(i + 1, len)), b.concat(a[i]), res)
       , i++
    );
  return res
}

p(["a", "b", "c"]);

当前问题试图通过线性处理依赖于先前的排列来创建给定的排列。

例如,给定一个数组

var arr = ["a", "b", "c"];

确定可能的排列总数。
for (var len = 1, i = k = arr.length; len < i ; k *= len++);

"k" 应该返回 "6",或者是数组 ["a", "b", "c"] 可能的所有排列总数。
有了确定集合的个体排列总数,可以使用 Array.prototype.slice(),Array.prototype.concat() 和 Array.prototype.reverse() 创建并填充包含所有六个排列的结果数组。
var res = new Array(new Array(k));

res[0] = arr;

res[1] = res[0].slice(0,1).concat(res[0].slice(-2).reverse());

res[2] = res[1].slice(-1).concat(res[1].slice(0,2));

res[3] = res[2].slice(0,1).concat(res[2].slice(-2).reverse());

res[4] = res[3].slice(-2).concat(res[3].slice(0,1));

res[5] = res[4].slice(0,1).concat(res[4].slice(-2).reverse());

尝试根据在计算排列和面试题中基于《C++实用算法》中发布的一种有序字典排序算法的图形显示的模式来复制结果。
如果输入集是例如["a", "b", "c", "d", "e"],则可能会出现可以扩展的模式,预期将产生120种排列。
下面是一个仅依赖于先前排列填充数组的示例。
// returns duplicate entries at `j`
var arr = ["a", "b", "c", "d", "e"], j = [];
var i = k = arr.length;
arr.forEach(function(a, b, array) {
 if (b > 1) {
  k *= b;
  if (b === i -1) {
    for (var q = 0;j.length < k;q++) {
      if (q === 0) {
       j[q] = array;
      } else {
       j[q] = !(q % i) 
              ? array.slice(q % i).reverse().concat(array.slice(0, q % i)) 
              : array.slice(q % i).concat(array.slice(0, q % i));
      }
    }
  }
 }
})

然而,我们还没有能够在上面的js中对.slice()、.concat()、.reverse()进行必要的参数调整,以便从一个排列步进到下一个;只使用前一个数组条目来确定当前排列,而不使用递归。注意到奇偶调用的平衡,并尝试使用模数%运算符和输入数组.length来调用.reverse()或不调用["a","b","c","d","e"]数组,但没有产生没有重复条目的结果。期望的结果是可以将上述模式简化为两行连续调用的持续时间,直到所有排列完成,res填充;一次调用.reverse(),一次不调用.reverse();例如,在res[0]填充后。
// odd , how to adjust `.slice()` , `.concat()` parameters 
// for array of unknown `n` `.length` ?
res[i] = res[i - 1].slice(0,1).concat(res[i - 1].slice(-2).reverse());
// even    
res[i] = res[1 - 1].slice(-1).concat(res[i - 1].slice(0,2));

问题:如何调整上述模式,特别是传递给 .slice().concat() 的参数或索引,以生成给定集合的所有可能排列,而无需使用对当前处理函数的递归调用?

var arr = ["a", "b", "c"];

for (var len = 1, i = k = arr.length; len < i; k *= len++);

var res = new Array(new Array(k));

res[0] = arr;

res[1] = res[0].slice(0, 1).concat(res[0].slice(-2).reverse());

res[2] = res[1].slice(-1).concat(res[1].slice(0, 2));

res[3] = res[2].slice(0, 1).concat(res[2].slice(-2).reverse());

res[4] = res[3].slice(-2).concat(res[3].slice(0, 1));

res[5] = res[4].slice(0, 1).concat(res[4].slice(-2).reverse());

console.log(res);


编辑,更新

已找到一种利用上述模式返回按字典顺序排列的置换的过程,对于长度为.length 4 的输入,使用单个for循环。对于长度为5的数组,未返回预期结果。

该模式基于“计算排列和面试问题”的第二张图表[0]

希望不使用.splice().sort()来返回结果,尽管在尝试遵守每列最后一个“旋转”要求时在此处使用了这些方法。变量r应引用下一个排列的第一个元素的index,它确实引用了。

如果.splice().sort()的使用遵循图表中的模式,则可以包含它们的使用;但是,在下面的js中,它们实际上并没有遵循。

我不完全确定下面的js问题是否仅限于if (i % (total / len) === reset)语句后面的内容,尽管这部分需要最多的时间投入;但仍然没有返回预期结果。

具体来说,现在参考图表,在旋转时,例如将2旋转到索引0,将1旋转到索引2。试图通过使用负索引r从右向左遍历以检索应该定位在相邻“列”的index0处的下一个项目来实现这一点。

在下一列中,2将被放置在index23将被放置在index0。就目前为止,我已经能够掌握或调试的部分是出现错误的区域。

同样,对于[1,2,3,4]返回预期结果,但对于[1,2,3,4,5]则不是。

var arr = [1, 2, 3, 4];
for (var l = 1, j = total = arr.length; l < j ; total *= l++);
for (var i = 1
     , reset = 0
     , idx = 0
     , r = 0
     , len = arr.length
     , res = [arr]
     ; i < total; i++) {
  // previous permutation
  var prev = res[i - 1];
  // if we are at permutation `6` here, or, completion of all 
  // permutations beginning with `1`;
  // setting next "column", place `2` at `index` 0;
  // following all permutations beginning with `2`, place `3` at
  // `index` `0`; with same process for `3` to `4`
  if (i % (total / len) === reset) {
    r = --r % -(len);
    var next = prev.slice(r);
    if (r === -1) {
      // first implementation used for setting item at index `-1`
      // to `index` 0
      // would prefer to use single process for all "rotations",
      // instead of splitting into `if` , `else`, though not there, yet
      res[i] = [next[0]].concat(prev.slice(0, 1), prev.slice(1, len - 1)
               .reverse());
    } else {
      // workaround for "rotation" at from `index` `r` to `index` `0`
      // the chart does not actually use the previous permutation here,
      // but rather, the first permutation of that particular "column";
      // here, using `r` `,i`, `len`, would be 
      // `res[i - (i - 1) % (total / len)]`
      var curr = prev.slice();
      // this may be useful, to retrieve `r`, 
      // `prev` without item at `r` `index`
      curr.splice(prev.indexOf(next[0]), 1);
      // this is not optiomal
      curr.sort(function(a, b) {
        return arr.indexOf(a) > arr.indexOf(b)
      });
      // place `next[0]` at `index` `0`
      // place remainder of sorted array at `index` `1` - n
      curr.splice(0, 0, next[0])
      res[i] = curr
    }
    idx = reset;
  } else {
    if (i % 2) {
      // odd
      res[i] = prev.slice(0, len - 2).concat(prev.slice(-2)
              .reverse())
    } else {
      //  even
      --idx
      res[i] = prev.slice(0, len - (len - 1))
               .concat(prev.slice(idx), prev.slice(1, len + (idx)))
    }
  }
}
// try with `arr` : `[1,2,3,4,5]` to return `res` that is not correct;
// how can above `js` be adjusted to return correct results for `[1,2,3,4,5]` ?
console.log(res, res.length)


资源:

使用Javascript生成排列

(倒计时) QuickPerm头部词典排序: (正式示例_03 〜 回文)

生成所有排列[非递归] (试图将C ++移植到javascript jsfiddle http://jsfiddle.net/tvvvjf3p/)

无需递归计算排列 - 第2部分

使用迭代的字符串排列

迭代排列

交换排列法

排列算法的评估

不使用递归的排列算法?Java

具有重复元素的全排列非递归算法?

Java中的字符串排列(非递归)

懒惰生成排列

如何在Python中生成列表的所有排列

一个集合或字符串的所有排列是否可以在O(n log n)时间内生成?

找到'0123456789'的第n个字典序排列

组合与排列


2
Quora: What's an algorithm to print the permutations of a string without recursion?上有一个算法可以帮助你。你也可以搜索其他语言的算法并将它们转换为ECMAScript。 - RobG
@Spektre 如果可能的话,能否将解决方案制定为javascript,并在当前问题中以描述所采用的过程和方法的方式发布答案? - guest271314
@guest271314 抱歉,我不会编写JavaScript代码。这就是为什么我只使用注释的原因,但该线程还包含有关如何使用示例的说明,因此对于任何在其中编码的人来说,将其移植到JavaScript应该不太困难。 - Spektre
2
斯坦豪斯-约翰逊-特罗特算法可能会引起您的兴趣,https://en.m.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm - גלעד ברקן
9个回答

20

这里是一个简单的方法来计算一个字符串的第n个排列:

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;
}

该算法遵循以下简单步骤:

  • 首先计算 f = len!,一个不同元素的集合有 len 种排列,共有 factorial(len) 个排列。
  • 作为第一个元素,将排列号除以 (len-1)!,并选择偏移量得到的元素。对于任何给定元素作为其第一个元素的排列,都有 (len-1)! 种不同的排列。
  • 从集合中删除所选元素,并使用除法余数作为下一个排列号继续进行。
  • 使用减少一的长度处理剩余集合。

此算法非常简单且具有有趣的特性:

  • 它可以直接计算第n个排列。
  • 如果集合已排序,则按字典顺序生成排列。
  • 即使集合元素无法相互比较,如对象、数组、函数等,它也能正常工作。
  • 排列号0 是按给定顺序排列的集合。
  • 排列号factorial(a.length)-1 是最后一个排列:按反向顺序排列的集合a
  • 范围之外的排列将返回未定义。

它很容易转换为处理存储为数组的集合:

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;
}

澄清:

  • 首先,将f计算为factorial(len)
  • 对于每一步,f都会被len除以,得到上一个阶乘。
  • n除以这个新值的f,可以得到与具有相同初始元素的len个插槽中的哪个插槽相对应的插槽编号。由于JavaScript没有整数除法,我们可以使用 (n / f) ... 0) 将除法结果转换为其整数部分,但这会使得集合限制为12个元素。 Math.floor(n / f)可处理多达18个元素的集合。我们也可以使用(n - n % f) / f,这可能更有效率。
  • 必须将n减少到该插槽内的排列数,即除法n / f的余数。

在第二个循环中,我们可以以不同的方式使用变量i,存储除法余数,从而避免使用Math.floor()和额外的%运算符。以下是这个循环的另一种可能更不易读的替代方案:

        // 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;
        }

@chqlie 如果可能的话,能否在第二个 for 循环中包含注释,描述每行发生的过程? - guest271314
能否描述 i = (n / (f /= len)) >> 0n -= f * i; 的数学和编程操作? - guest271314
1
确实如此。如果x在范围]-2147483649 .. 2147483648[内,则x >>= 0将浮点数转换为其整数部分。它的无符号等效项x >>>= 0首先将x转换为UInt32,因此适用于范围[0 .. 4294967296[内的x。但在您的情况下,我们应该能够处理超过12个元素的集合,从而超出了32位整数的容量。使用Math.floor()允许处理多达18个元素的集合。 - chqrlie
1
@guest271314: (n - n % f) / f 可以计算整数除法,无需在其周围调用 Math.floor。它执行了一个额外的模运算,但避免了昂贵的查找操作 Math.floor、函数调用开销和实际的 floor 浮点运算。所有这些都可以进行优化,但可能仍然比 (n - n % f) / f 慢。 - chqrlie
1
如果您使用(n - n % f) / f,则需要在另一条语句中计算f /= len - chqrlie
显示剩余9条评论

9
我认为这个post应该会对你有所帮助。该算法应该很容易转换成JavaScript(我认为它已经超过70%与JavaScript兼容)。
如果你追求效率,那么使用slicereverse是不好的调用。该文章中描述的算法遵循了next_permutation函数的最高效实现方式,甚至在某些编程语言中已经集成(例如C++)。
编辑:
当我再次迭代该算法时,我认为你可以只删除变量类型,然后就可以在JavaScript中使用了。
JavaScript版本:
function nextPermutation(array) {
    // Find non-increasing suffix
    var i = array.length - 1;
    while (i > 0 && array[i - 1] >= array[i])
        i--;
    if (i <= 0)
        return false;

    // Find successor to pivot
    var j = array.length - 1;
    while (array[j] <= array[i - 1])
        j--;
    var temp = array[i - 1];
    array[i - 1] = array[j];
    array[j] = temp;

    // Reverse suffix
    j = array.length - 1;
    while (i < j) {
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;
        i++;
        j--;
    }
    return true;
}

7

创建排列的一种方法是在到目前为止所有结果的所有元素之间的所有空格中添加每个元素。可以使用循环和队列而不使用递归来完成此操作。

JavaScript 代码:

function ps(a){
  var res = [[]];

  for (var i=0; i<a.length; i++){
    while(res[res.length-1].length == i){
      var l = res.pop();
      for (var j=0; j<=l.length; j++){
        var copy = l.slice();
        copy.splice(j,0,a[i]);
        res.unshift(copy);
      }
    }
  }
  return res;
}

console.log(JSON.stringify(ps(['a','b','c','d'])));

在解决方案中可以做哪些调整以按字典顺序返回结果? - guest271314
@guest271314 我不确定这个算法是否可以调整为按字典顺序输出排列。然而,已知有一种“下一个字典顺序排列”的算法:https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order - גלעד ברקן
能够使用 .sort() 对结果进行字典序排序,但会尝试在循环内使用其他方法。 - guest271314
@guest271314 不太清楚你的意思 - 那段代码片段对我来说没有起作用 - גלעד ברקן
让我们在聊天室继续这个讨论. - guest271314
显示剩余3条评论

6

以下是一种可能的解决方案,灵感来源于斯坦豪斯-约翰逊-特罗特算法

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 />");
}


5

这里是我自己想出来的一种方法,但自然也能在其他地方找到它的描述

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>

解释

对于给定的数组arr,存在factorial(arr.length)个排列方式,每个数字在0factorial(arr.length)-1之间都代表一种特定的排列方式。要解码排列数字,需要不断从arr中删除元素,直到没有元素为止。要删除的确切索引由公式(permNumber % factorial(arr.length)) / factorial(arr.length-1)给出。只要保留数字和排列之间的一一映射关系,就可以使用其他公式确定要删除的索引。

示例

以下是如何生成数组(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]

请注意,每个排列号的形式为:
(firstElIndex * 3!) + (secondElIndex * 2!) + (thirdElIndex * 1!) + (fourthElIndex * 0!)

这基本上是解释中提供的公式的反向过程。

5

我敢再添加一个答案,回答你关于sliceconcatreverse的问题。

答案是几乎可以,但不会很有效。在你的算法中,你正在做以下操作:

  • 从右到左找到排列数组中的第一个逆序(在这种情况下,逆序被定义为ij,其中i<jperm[i]>perm[j],索引从左到右给出)
  • 放置逆序中较大的数字
  • 连接反转顺序处理过的数字,这将与排序顺序相同,因为没有观察到逆序。
  • 连接逆序的第二个数字(仍按先前的数字排序,因为没有观察到逆序)

这基本上是我的第一个答案所做的事情,但以更优化的方式。

示例

考虑排列9,10,11,8,7,6,5,4,3,2,1 从右到左的第一个逆序是10,11。 实际上下一个排列是: 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)

源代码 这里我包括我设想的源代码:

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]));

这段代码可以在jsfiddle上找到,链接在此


是的,尝试使用.slice(), .reverse(), .concat() 和单个for循环。试图将 OP 的递归解决方案转换为迭代解决方案,但尚未能够实现。您最初的回答实际上要求进一步探究;使用类似模式创建了一个工作版本。同样,我不敢在这里发布它,因为它实际上没有解决 OP 描述的模式。 - guest271314
迭代,非递归。考虑排列9,10,11,8,7,6,5,4,3,2,1。从右到左的第一个反序对是10,11。实际上,下一个排列是: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)。这是否可以通过对长格式方法或.forEach()方法进行修改来实现?创建了一个包括input[i] < input[i + 1]for,但在第六个排列后无法返回预期结果,尝试了偶数奇数方法,但至今未能使用。 - guest271314
尝试在for循环内缩减到一行或两行; 初始尝试不完整,未返回预期结果 http://jsfiddle.net/ya31o4km/2/。在问题的长格式中注意到模式,在“res [2] = res [1] .slice(-1) .concat(res [1] .slice(0,2));”处应用了“-1”; ni在jsfiddle上应该能够在六次迭代中用于.slice()递减?观察模式时漏掉了什么,为什么发布问题? - guest271314
1
@guest271314,我已经添加了我的代码,还没有机会尝试它。 - Boris Strandjev
目前正在将.push()替换为.concat()。尝试扩展以返回所有排列?将下一个参数设置为返回的数组arr?或者这个过程已经包含在内了吗? - guest271314
显示剩余2条评论

2

一个没有递归的相当简单的C++代码。

Original Answer翻译成"最初的回答"。
#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;
}

我们可以在线性时间内找到一个序列的下一个排列。最初的回答中提到了这一点。

代码仅处理字符串输入吗?代码中是否有无法使用Emscripten转换为JavaScript的部分? - guest271314
实际上,这段代码处理整数数据。同样的算法也可以用于字符串。 - Venki
预期的输入是什么? - guest271314
添加了整数和字符(字符串)的示例代码,以及运行代码的主函数。 - Venki

0

这里有一个答案, 来自@le_m。它可能会有所帮助。

下面这个非常高效的算法使用Heap's方法生成所有N个元素的排列,时间复杂度为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

您可以使用栈来遍历排列。

这种方法在处理树或其他问题时非常理想,而不依赖于递归。 您需要进行调整,以避免重复值。

type permutation = [string, string[]]
function p(str: string): string[]{
  const res: string[] = []
  const stack: permutation[] = [["", str.split('')]]

  while(stack.length){
    const [head, tail] = stack.pop()

    if(!tail.length){
      res.push(head)
      continue
    }

    for(let i = 0; i < tail.length; i++){
      let newTail = tail.slice()
      newTail.splice(i, 1)
      stack.push([head + tail[i], newTail])
    }
  }

  return res
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接