排列没有递归函数调用

问题描述 投票:29回答:8

要求:算法以生成一组的所有可能的组合,没有重复,或递归调用函数返回的结果。

大多数,如果不是全部的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);

编辑,更新

已经找到一种方法,利用上述返回在字典顺序排列,用于将输入到.length 4,使用单个for环形图案。预期结果不返回与.length5阵列。

所述图案在[0]“计算排列和面试问题”是基于在第二图表上。

宁愿不使用.splice().sort()返回结果,尽管这里使用,同时试图坚持到最后每列“旋转”的要求。可变r应参照下一个排列,其中它的第一个元素的index

使用.splice()的,.sort()可以如果它们的用法,接着在图表的图案被包括;虽然在下面js,他们实际上没有。

不完全肯定的是,下面js的问题只是以下if (i % (total / len) === reset)的声明,尽管这部分所需的时间最有投资;但仍没有返回预期结果。

具体地,现在参照该图表,在旋转时,例如向2索引01索引2。试图利用r,这是一个负折射率,从右到横穿至左,以检索应该位于相邻“列”的index 0下一个项目来实现这一点。

在下一栏,2将被放置在index 23将被放置在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)

资源:

Generating Permutation with Javascript

(Countdown) QuickPerm Head Lexicography: (Formally Example_03 ~ Palindromes)

Generating all Permutations [non-recursive](尝试端口从C++javascript的jsfiddle http://jsfiddle.net/tvvvjf3p/

Calculating Permutation without Recursion - Part 2

permutations of a string using iteration

iterative-permutation

Permutations by swapping

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?

Finding the nth lexicographic permutation of ‘0123456789’

Combinations and Permutations

javascript algorithm permutation
8个回答
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;
}

该算法遵循这些简单的步骤:

  • 第一计算机qazxsw POI,有一组不同f = len!元件的qazxsw POI总置换。
  • 作为第一个元素,除以factorial(len)置换数量和选择的元素在结果偏移量。有迹象表明有任何给定的元素作为第一元素len不同的排列。
  • 从该组中删除所选择的元件,并使用除法的余数作为该置换数继续下去。
  • 执行与所述一组,其长度由一个减小的其余部分这些步骤。

这种算法非常简单,有趣的性质:

  • 它直接计算第n个置换。
  • 如果设置是有序的,在字典顺序生成的排列。
  • 它的工作原理,即使集合中的元素不能相互比较,如对象,数组,函数...
  • 排列数(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; }
  • 对于每个步骤,qazxsw POI是由qazxsw POI划分,恰好使先前阶乘。
  • f通过factorial(len)的这个新值除以给出具有相同的初始元件f时隙中的时隙号。 JavaScript没有整数分频,我们可以使用len来划分的结果转换为它的组成部分,但它引入了一个限制组的12个元素。 n允许套多达18元件。我们也可以使用f,可能更有效了。
  • len必须降低到该插槽内的置换数,即除法(n / f) ... 0)的其余部分。

我们可以在第二循环中不同使用Math.floor(n / f),存储余数,避免(n - n % f) / f和额外n操作。下面是该环可能甚至更少可读的替代:

n / f

8
投票

我想,这应该i帮助你。该算法应该很容易翻译成JavaScript(我认为这是70%以上,已经JavaScript的兼容)。

Math.floor()%是坏的电话,如果你是效率后使用。在后描述的算法是继最高效的执行next_permutation功能,即使集成在一些编程语言(如C ++例如)

编辑

当我遍历算法再次我觉得你可以只取出类型的变量,你要善于在JavaScript中去。

编辑

JavaScript版本:

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

7
投票

创建排列的一种方法是通过迄今为止在所有元件之间的空间中的所有结果的添加每个元素。这可以在不使用递归循环和队列来完成。

JavaScript代码:

post

6
投票

这可能是另一种解决方案,从slice启发:

reverse

5
投票

下面是我想到了我自己的方法的摘要,但自然也能找到它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; }

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 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 />"); } 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>之间的每个数字编码的特定置换。要unencode置换数量,反复从factorial(arr.length)直到没有剩下的元素删除元素。该元件以去除的精确指数由下式给定arr。其它公式可以用于确定索引以除去,只要它保留数目和排列之间的一个一对一的映射。

下面是怎么回事排列将为阵列0产生:

factorial(arr.length)-1

需要注意的是每个排列#的形式是:

arr

这基本上是在说明中给出的公式的逆过程。


5
投票

我敢添加另一种答案,旨在回答您的问题有关(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!) slice其中concat <reversei> 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)

源代码在这里,我包括源代码,因为我想象它:

i

该代码是可以的jsfiddle qazxsw POI。


3
投票

一个相当简单的C ++代码,而无需递归。

j

我们可以发现线性时间序列的下一个排列。


0
投票

perm[i]从@le_m。它可能会有所帮助。

下面的非常有效的算法使用perm[j]产生为O与运行的复杂N个元素的所有排列(N!):

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]));
© www.soinside.com 2019 - 2024. All rights reserved.