web-dev-qa-db-ja.com

再帰的な関数呼び出しのない順列

要件:重複することなく、セットのすべての可能な組み合わせを生成するアルゴリズム、または結果を返すために関数を再帰的に呼び出すアルゴリズム。

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)_

リソース:

Javascriptを使用した順列の生成

(カウントダウン)QuickPerm Head Lexicography :(正式にはExample_03〜Palindromes)

すべての順列の生成[非再帰的] (_C++_からjavascript jsfiddleへの移植を試みます http://jsfiddle.net/tvvvjf3p/ =)

再帰なしの順列の計算-パート2

反復を使用した文字列の順列

反復順列

スワッピングによる順列

順列アルゴリズムの評価

再帰のない順列アルゴリズム?Java

反復要素を使用した完全な順列の非再帰的アルゴリズム?

Java(非再帰的) の文字列順列

順列を怠惰に生成する

Pythonでリストのすべての順列を生成する方法

セットまたは文字列のすべての順列をO(n log n)時間で生成できますか?

「0123456789」のn番目の辞書式順列を見つける

組み合わせと順列

31
guest271314

文字列の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)!_の異なる順列があります。
  • 選択した要素をセットから削除し、除算の余りを順列番号として使用して続行します。
  • これらの手順は、長さが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)として計算されます。
  • 各ステップで、flenで除算され、正確に前の階乗が得られます。
  • 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;
        }
_
19
chqrlie

私はこれが 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;
}
8
Boris Strandjev

順列を作成する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'])));
7

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

sliceconcatreverseに関する質問に答えることを目的として、あえて別の答えを追加します。

答えは(ほぼ)可能ですが、あまり効果的ではありません。アルゴリズムで行っていることは次のとおりです。

  • 順列配列の最初の反転を右から左に検索します(この場合の反転は、iおよびjとして定義されます。ここで、i <jおよびperm[i]> perm[j]、左から右に指定されたインデックス)
  • より多くの反転を配置します
  • 処理された番号を逆の順序で連結します。これは、反転が観察されなかったため、ソートされた順序と同じになります。
  • 反転の2番目の番号を連結します(反転が観察されなかったため、以前の番号に従って並べ替えられます)

これは主に、私の最初の答えが行うことですが、もう少し最適な方法です。

順列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で利用できます ここ

5
Boris Strandjev

これは私が自分で思いついたアプローチのスニペットですが、当然それを見つけることもできました 他の場所で説明されています

_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!)
_

これは基本的に、説明で与えられた式の逆のプロセスです。

5
heenenee

再帰のない非常に単純な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;
}

線形時間でシーケンスの次の順列を見つけることができます。

3
Venki

ここに答えがあります@ 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])));
0
jo_va