要求:生成集合的所有可能组合的算法,无需重复,或递归调用函数以返回结果。
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
,或arr
["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());
尝试根据有序词典排列算法图表中显示的模式重现结果,该算法基于Calculating Permutations and Job Interview Questions在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));
}
}
}
}
})
然而,尚未能够在.slice()
,.concat()
,.reverse()
的参数上进行必要的调整,从js
开始,从一个排列步进到下一个排列;而只使用res
中的前一个数组条目来确定当前的排列,而不使用递归。
注意甚至,奇怪的调用平衡并尝试使用模%
运算符和输入数组.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);
&#13;
编辑,更新
使用单个.length
循环找到了一个利用上述模式的流程,以按字典顺序返回最多for
4的输入的排列。对于.length
5
的数组,不会返回预期结果。
该模式基于&#34;计算排列和求职面试问题的第二个图表&#34; [0] 。
不希望使用.splice()
或.sort()
来返回结果,尽管此处在尝试遵守最后&#34;旋转&#34; 要求时使用柱。变量r
应该引用下一个排列的第一个元素的index
。
如果.splice()
,.sort()
的使用遵循图表中的模式,则可以使用js
,js
;虽然在if (i % (total / len) === reset)
以下,但他们实际上并没有。
不完全确定下面2
的问题只是0
之后的声明,尽管该部分需要最多的时间投入;但仍未返回预期结果。
具体而言,现在参考图表,在旋转时,例如1
索引2
,r
索引index
。试图通过使用0
(负索引)来实现此目的,从右到左遍历以检索应位于相邻&#34;列的2
index
的下一个项目& #34;
在下一列,2
将放置在3
index
,0
将放置在[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)
的预期结果,但不是C++
javascript
&#13;
资源:
Generating Permutation with Javascript
(Countdown) QuickPerm Head Lexicography: (Formally Example_03 ~ Palindromes)
Generating all Permutations [non-recursive] (尝试从{{1}}移植到{{1}} jsfiddle http://jsfiddle.net/tvvvjf3p/)
Calculating Permutation without Recursion - Part 2
permutations of a string using iteration
Evaluation of permutation algorithms
Permutation algorithm without recursion? Java
Non-recursive algorithm for full permutation with repetitive elements?
String permutations in Java (non-recursive)
Generating permutations lazily
How to generate all permutations of a list in Python
Can all permutations of a set or string be generated in O(n log n) time?
答案 0 :(得分:19)
这是一个计算字符串第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!
,一组factorial(len)
个不同元素的总len
个排列。 (len-1)!
,并在结果偏移处选择元素。有(len-1)!
个不同的排列,任何给定的元素都是第一个元素。此算法非常简单,并且具有有趣的属性:
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;
}
答案 1 :(得分:8)
我认为这post可以帮到你。该算法应该可以轻松转换为JavaScript(我认为它已经超过70%已经与JavaScript兼容)。
如果您追求效率, slice
和reverse
是不好的使用方式。帖子中描述的算法遵循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;
}
答案 2 :(得分: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'])));
答案 3 :(得分:6)
这可能是另一个解决方案,灵感来自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 />");
}
答案 4 :(得分:5)
这是我自己提出的方法的片段,但自然也能找到它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;
};
&#13;
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>
&#13;
<强>解释强>
由于给定数组factorial(arr.length)
有arr
个排列,0
和factorial(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 :(得分:5)
我敢于添加另一个答案,旨在回答有关slice
,concat
,reverse
的问题。
答案是(几乎)可能,但它不会很有效。您在算法中执行的操作如下:
i
和j
其中i
&lt; j
和{ {1}}&gt; perm[i]
,从左到右给出的索引这主要是我的第一个答案,但是以更优化的方式。
示例强>
考虑排列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)
源代码 在这里,我按照我的设想包含了源代码:
perm[j]
该代码适用于jsfiddle here。
答案 6 :(得分:3)
相当简单的C ++代码,无需递归。
#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;
}
我们可以找到线性时间序列的下一个排列。
答案 7 :(得分:0)
Here is an answer 来自@le_m 。可能会有帮助。
以下非常有效的算法使用Heap's method生成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])));