没有递归函数调用的排列

IT技术 javascript algorithm permutation
2021-02-12 19:16:58

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

在 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());

试图再现基于模式结果在图表中显示的基础上发表的一项实用算法中的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()产生一组给定的所有可能的排列,而无需使用递归调用当前处理功能?


编辑、更新

已经找到了一个过程,可以使用上述模式.length使用单个for循环返回按字典顺序排列的排列,输入最多为4 不为具有.lengthof 的数组返回预期结果5

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

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

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

不完全确定js下面的问题只是下面的陈述if (i % (total / len) === reset),尽管那部分需要最多的时间投入;但仍然没有返回预期的结果。

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

在下一列,2将被放置在index 23将被放置在index 0这部分,就目前为止已经能够掌握或调试的,是发生错误的区域。

再次返回 的预期结果[1,2,3,4],但不是 for[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: (Formally Example_03 ~ Palindromes)

生成所有排列 [非递归] (尝试移植到 fromC++javascriptjsfiddle http://jsfiddle.net/tvvvjf3p/

不使用递归计算排列 - 第 2 部分

使用迭代的字符串排列

迭代排列

通过交换排列

置换算法的评估

没有递归的置换算法?Java

用于重复元素完全排列的非递归算法?

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

懒惰地生成排列

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

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

查找 '0123456789' 的第 n 个词典排列

组合和排列

6个回答

这是计算字符串的第 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)!不同的排列将任何给定元素作为它们的第一个元素。
  • 从集合中删除所选元素并使用除法的余数作为排列数继续前进。
  • 使用该组的其余部分执行这些步骤,其长度减一。

这个算法非常简单并且有一些有趣的特性:

  • 它直接计算第 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;
        }
确实是的。x >>= 0如果浮点数在范围内,则将浮点数转换为其整数部分]-2147483649 .. 2147483648[x >>>= 0,其无符号等价物首先转换xUInt32,因此适用x于范围内[0 .. 4294967296[但是在您的情况下,我们应该能够处理超过 12 个元素的集合,从而超过 32 位整数的容量。使用Math.floor()允许设置多达 18 个元素。
2021-03-16 19:16:58
@chqlie 如果可能,可以在第二个for循环中包含注释,描述每一行发生的过程吗?
2021-03-18 19:16:58
@guest271314:如果使用(n - n % f) / f,则需要事先f /= len在单独的语句中进行计算
2021-04-07 19:16:58
可以描述数学,程序操作i = (n / (f /= len)) >> 0, 和n -= f * i;?
2021-04-12 19:16:58
@guest271314:(n - n % f) / f计算整数除法,无需调用Math.floor它。它执行一个额外的模运算,但避免了昂贵的查找操作Math.floor、函数调用开销和实际的floor浮点运算。所有这些都可以优化,但可能仍然比(n - n % f) / f.
2021-04-13 19:16:58

我认为这篇文章应该对你有所帮助。该算法应该很容易翻译成 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;
}

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

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'])));
return res.sort()似乎实现了字典顺序。任何情况下这会返回意外结果?也许.sort()也可以用于返回排列?,当在数组内的数组中植入具有.length总可能排列的数组时,其中每个内部数组都是原始输入数组?
2021-03-19 19:16:58
在解决方案中可以进行哪些调整以按字典顺序返回结果?
2021-03-29 19:16:58
sort()函数将尝试按字典(或用户定义)顺序对您提供的输入进行排序。我不知道如何使用它来返回排列 - 如果你sort()以不同的顺序给出两个具有相同元素的数组,sort()将返回两个相同的数组 - 都以相同的方式排序。
2021-04-04 19:16:58
@guest271314 我不确定是否可以调整此算法以按字典顺序输出排列。有一个已知的“下一个词典排列”算法,但是:en.wikipedia.org/wiki/...
2021-04-05 19:16:58
能够使用.sort()结果转换为字典顺序,但会在循环中尝试其他方法
2021-04-06 19:16:58

这可能是另一种解决方案,灵感来自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 />");
}

这是我自己提出的一种方法的片段,但自然也能在别处找到它的描述

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>

解释

由于factorial(arr.length)给定数组存在排列,因此arr之间的每个数字都编码特定排列。要取消对排列数的编码,请重复从中删除元素,直到没有元素为止。要删除的元素的确切索引由公式给出可以使用其他公式来确定要删除的索引,只要它保留数字和排列之间的一对一映射即可。0factorial(arr.length)-1arr(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!)

这基本上是解释中给出的公式的逆过程。