要件:重複することなく、セットのすべての可能な組み合わせを生成するアルゴリズム、または結果を返すために関数を再帰的に呼び出すアルゴリズム。
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"]
_の可能な順列の総数を返す必要があります
セットに対して決定された個々の順列の総数を使用して、6つの順列すべてを含む結果の配列を作成し、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++のPractical Algorithmsで公開されているものに基づく順序付き辞書式順列アルゴリズムのグラフに表示されたパターンに基づいて結果を再現しようとしました 順列と就職の面接の質問の計算 。
たとえば、入力セットが次の場合に拡張できるパターンがあるようです。
_["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()
のパラメーターで、1つの順列からステップするために必要な調整をまだ行うことができていません。次へ ; res
内の前の配列エントリのみを使用して、再帰を使用せずに現在の順列を決定します。
呼び出しの偶数、奇数のバランスに気づき、モジュラス_%
_演算子と入力配列_.length
_を使用して、.reverse()
を呼び出すか、_["a", "b", "c", "d", "e"]
_配列で呼び出さないようにしましたが、生成されませんでした重複エントリのない結果。
期待される結果は、上記のパターンを、すべての順列が完了し、res
が満たされるまで、プロセスの期間中、連続して呼び出される2行に減らすことができるということです。 .reverse()
を呼び出すために1つずつ、.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);
_
編集、更新
上記のパターンを利用して、単一のfor
ループを使用して、_.length
_ 4までの入力の辞書式順序で順列を返すプロセスを見つけました。 _.length
_が_5
_の配列では、期待される結果は返されません。
このパターンは、「順列と就職の面接の質問の計算」の2番目のグラフに基づいています。[]。
.splice()
または.sort()
を使用して結果を返すことは避けたいと思いますが、ここでは最後の "rotate"各列の要件。変数r
は、次の順列の最初の要素のindex
を参照する必要があります。
.splice()
、.sort()
の使用は、それらの使用がチャートのパターンに従っている場合に含めることができます。ただし、以下のjs
では、実際にはそうではありません。
以下のjs
の問題が、if (i % (total / len) === reset)
に続くステートメントだけであるかどうかは完全にはわかりませんが、その部分には最も多くの時間の投資が必要でした。それでも、期待される結果は返されません。
具体的には、チャートを参照して、回転時に、たとえば_2
_をインデックス_0
_に、_1
_をインデックス_2
_に参照します。負のインデックスであるr
を使用してこれを達成しようとしました。右から左にトラバースして、隣接する「列」のindex
_0
_に配置する必要がある次のアイテムを取得します。 。
次の列では、_2
_はindex
_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)
_
リソース:
(カウントダウン)QuickPerm Head Lexicography :(正式にはExample_03〜Palindromes)
すべての順列の生成[非再帰的] (_C++
_からjavascript
jsfiddleへの移植を試みます http://jsfiddle.net/tvvvjf3p/ =)
Java(非再帰的) の文字列順列
文字列の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)!
_の異なる順列があります。このアルゴリズムは非常に単純で、興味深い特性があります。
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
_に減らす必要があります。2番目のループでは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;
}
_
私はこれが 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;
}
順列を作成する1つの方法は、これまでのすべての結果の要素間のすべてのスペースに各要素を追加することです。これは、ループとキューを使用して再帰なしで実行できます。
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'])));
Steinhaus-Johnson-Trotterアルゴリズム から着想を得た別の解決策があります:
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 />");
}
slice
、concat
、reverse
に関する質問に答えることを目的として、あえて別の答えを追加します。
答えは(ほぼ)可能ですが、あまり効果的ではありません。アルゴリズムで行っていることは次のとおりです。
i
およびj
として定義されます。ここで、i
<j
およびperm[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で利用できます ここ 。
これは私が自分で思いついたアプローチのスニペットですが、当然それを見つけることもできました 他の場所で説明されています :
_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)
順列があるため、_0
_とfactorial(arr.length)-1
の間の各数値は特定の順列をエンコードします。順列番号のエンコードを解除するには、要素がなくなるまでarr
から要素を繰り返し削除します。削除する要素の正確なインデックスは、式_(permNumber % factorial(arr.length)) / factorial(arr.length-1)
_で与えられます。数と順列の間の1対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!)
_
これは基本的に、説明で与えられた式の逆のプロセスです。
再帰のない非常に単純な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;
}
線形時間でシーケンスの次の順列を見つけることができます。
ここに答えがあります@ le_mから。それは助けになるかもしれません。
次の非常に効率的なアルゴリズムは、 ヒープの方法 を使用して、O(N!)で実行時の複雑さを持つ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])));