【问题标题】:Get the n-th permutation of a large set of elements获取大量元素的第 n 次排列
【发布时间】:2016-10-07 14:57:03
【问题描述】:

我想编写一个 JavaScript 程序,以所有可能的排列方式排列一组 36 个数字(每秒 10 个排列)。排列这些数字有 36 种阶乘方式。

我知道我的程序要到世界末日才会结束 :)(这正是我想要展示的)。

它从以下顺序开始:

  1. 排列:0,1,2,3,4,5,6,...,32,33,34,35
  2. 排列:0,1,2,3,4,5,6,...,32,33,35,34
  3. 排列:0,1,2,3,4,5,6,...,32,34,33,35

.....(这里有很多排列)......

有没有一种方法可以计算第 5'595'000'000 次(自 2000 年 1 月 1 日以来的十进制时间)排列而不计算所有以前的排列? 计算以前的那些确实需要永远!

另外,如果我知道第 5'595'000'000 个排列,我需要一种方法来计算下一个排列。

我发现的所有排列算法一次计算所有排列。这不是一个有这么多排列的选项。

这是可能的还是我注定要失败?

【问题讨论】:

  • 这是作业问题还是什么?您尝试过的代码在哪里?

标签: javascript algorithm permutation largenumber


【解决方案1】:

这里有两个函数:

  • getPermutationByRank:通过其秩数获取排列(数组)。灵感来自this answer
  • nextPermutation:将给定的排列改变为顺序中的下一个排列。灵感来自this answer

这基本上是你需要的,但我添加了一个生成器,它使用上述两个函数来生成从某个等级开始的排列。

最后,格式化函数将给定的排列转换为字符集(10 个数字 + 26 个字母 = 36 个不同的字符)。

sn-p 计算自 2000-01-01 以来的当前十进制秒数,并输出相应的排列,每经过十进制将其更新为下一个排列:

// See https://stackoverflow.com/a/7919887/5459839
function getPermutationByRank(n, rank) {
    // Sageguard for inaccurate calculations: rank <= 9007199254740991
    if (rank > Number.MAX_SAFE_INTEGER) throw "Too large rank for JavaScript";
    var perm = (function loop(i, fact) {
        // Calculate factorials and subtract from rank from highest to lowest
        return i > n ? [] :
               [loop(i+1, fact * i).concat(Math.floor(rank / fact) % n),
                rank = rank % fact][0];
    })(1, 1);
    // Readjust values to obtain the permutation
    // start from the end and check if preceding values are lower
    for (let k = n - 1; k > 0; --k)
      for (let j = k - 1; j >= 0; --j)
         if (perm[j] <= perm[k])
            perm[k]++;
    return perm;
}

// See https://stackoverflow.com/a/1622539/5459839:
function nextPermutation(perm) {
    // Find longest non-increasing suffix
    var i = perm.length - 2;
    while (i >= 0 && perm[i] >= perm[i + 1])
        i--;
    // Now i is the head index of the suffix
    
    // Are we at the last permutation already?
    if (i < 0) return; // no more permuations
    
    // Let perm[i] be the pivot
    // Find rightmost element that exceeds the pivot
    var j = perm.length - 1;
    while (perm[j] <= perm[i])
        j--;
    // Now the value perm[j] will become the new pivot
    // Assertion: j >= i
    
    // Swap the pivot with j
    [perm[i], perm[j]] = [perm[j], perm[i]];
    
    // Reverse the suffix
    j = perm.length - 1;
    i++;
    while (i < j) {
        [perm[i], perm[j]] = [perm[j], perm[i]];
        i++;
        j--;
    }
    return perm;
}

// Create a generator using above two functions:
function* generatePermutationsFrom(n, rank) {
    var perm = getPermutationByRank(n, rank);

    yield perm;
    while (nextPermutation(perm)) {
        yield perm;
    }
}

// Utility functions for OP specific requirements
function deciSecondsSince(dt) {
    return Math.floor((Date.now() - dt.getTime())/100);
}

function permToString(perm) {
    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    return perm.map ( v => chars[v] ).join('');
}

var deciSecs = deciSecondsSince(new Date('2000-01-01')); // ~5293000000 
var iter = generatePermutationsFrom(36, deciSecs);

// Output next permuation every decisecond:
document.body.textContent = permToString(iter.next().value);
setInterval(function () {
    document.body.textContent = permToString(iter.next().value);
}, 100);
body { font-family: monospace }

免责声明:

  1. 这适用于您询问的排名数字(如 5293000000),但如果排名超过 JavaScript 可以提供的精度,结果将是错误的。事实上,在第一个函数中计算的阶乘只精确到18!,之后它们就失去了精度。只要您的排名在 JavaScript 支持的范围内(大约 16 位),这对算法没有影响。因此,第一个函数有保障。

  2. setInterval 不是一种与时间保持同步的非常可靠的方法,因此一段时间后,迭代次数可能与自第一次迭代以来经过的实际十进制秒数不同步。但这对于您的目的似乎并不重要。如果是,请查看this answer 以弥补这种漂移

【讨论】:

  • 非常感谢您的回答。这完美解决了我的问题!也感谢您在免责声明中提供的其他信息 :) 我想我会让我的项目从 2016 年 1 月 1 日开始,所以数字会小一些。 setInterval (正如你所提到的)并不是一个真正的问题。
猜你喜欢
  • 1970-01-01
  • 2019-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多