如何提高生成下一个词典排列的算法的效率?

时间:2017-04-08 05:18:13

标签: javascript algorithm math language-agnostic permutation

这里必须注意的是,我在纸上手工进行了数学计算,以得出上述证明。我不确定单独使用现代计算机的媒体是否会证明这些证据。

"效率"的定义如下所用,意味着在最短的时间内完成算法的离散部分或整个算法。无论是在数学上,还是在程序上,还是在计算上。

在进一步检查从原始集合或当前排列生成下一个词典排列的程序时,在Permutations without recursive function call重新阅读@chqrlie的答案后,我开始通过在纸上写下索引进行查询观察可用于执行特定任务的索引之间的任何数学关系。

我发现了几个有趣的事实,其证据如下所示。

例如,当我们写下值

a,b,c

abc

或者编写代表值的索引

0,1,2

012

因为我们知道,给定一套

abc

我们可以通过交换集合的最后两个值或索引来生成下一个词典排列

acb

021

我们可以忽略可能是任何类型数据的值,并专注于使用索引进行检查,因为离散数字更适合关联可能的关系而不是可能无限多样化的值。

因此,使用原始集合的第二个词典索引

abc

我们可以将索引的值表示为数字,并观察

0,2,1

021

第一个索引是

012

第二个是

021

由于我们知道原始集的.length3,如果我们记住原始值集的.length,我们可以删除起始

0

将索引减少到数字的位置

12

21

分别。其中0可以作为原始集合的索引引用,以便在下一个操作的结果集小于原始集合时获取索引0的值。

当我们尝试绘制1221之间的潜在关系时,我们发现

21 - 12 = 9

当我们继续时,我们发现下一个词典索引是

102

减去以前的索引

102 - 21 = 81

其中102是下一个词典排列,表示为值

bac

它为我们提供了数字9之间的共同关系,用数字表示为

9

对于表示为数字的无限输入值集合,此关系是明显且可重现的。我们可以根据观察者的视角,将关系图表视为两个斜率,从结果排列集的第一个值开始绘制时,反转的顶点偏移

// graph 1
0,9,81

// graph 2
abc 012 0
acb 021 1 9
bac 102 2 81
bca 120 3 18
cab 201 4 81
cba 210 5 9

 /\/\
/    \

我们在这里可以观察到,倾斜斜率上的图上的数字与相应的下倾斜率处的数字相同,在可能的排列的总数除以一半的数量的反转之后,例如,对于总共六个排列,我们减去一个,剩下五个,记住我们仍然需要奇数组索引,我们使用倒顶点处的数字作为枢轴,留下四个索引,我们将其表示为倾斜和赤纬斜坡,分别。

我们在这里观察到,赤纬斜率图上的数字与相同y坐标处倾斜斜率的相应相邻角度相同。

因此,下面我演示了一个证据,即通过利用数字9的加法或乘法,可以计算,生成,过滤,得到无限输入集的无限集合

9

通过匹配仅包含输入数字索引号的数字,在集合中没有重复。

此外,我证明了只需要索引作为倾斜斜率上的数字,或者可能的排列总数除以2加1,就可以得出给定输入的排列总数。

正如本文前言所强调的那样,如果没有很多时间在纸上手工进行数学计算,这种计算可能是不可能的。简单的媒体屏幕不提供与在纸上逐个组成字符相同的媒体;能够从各种物理尺寸查看纸张。

使用编码语言的算法表达本身就是另一项任务。

以下是使用&#34; JavaScript&#34;实现的发现,证据和表达的进展。编程语言。第一个版本的RegExp@Tushar所指出的预期结果不准确,但已更正为RegExp,但错误的RegExp会返回相同的结果。< / p>

将输入作为数组

var arr = ["a", "b", "c", "d"];

// version 1

function getNextLexicographicPermutation(arr) {
  var len = arr.length;
  var idx = arr.map(function(_, index) {
    return index
  });
  var re = new RegExp("[" + len + "-9]|(.)(?!=\\1)");
  var n = Math.pow(10, len - 1);
  var p = 9;
  var last = Number(idx.slice().reverse().join(""));
  var res = [idx];
  var curr = Number(res[res.length - 1].join(""));
  while (curr < last) {
    for (var prev = curr, next, k; prev <= last; prev += p) {
      if ((re.test(prev))) {
        k = String(prev);
        if (k.length >= len - 1) { //  && Math.max.apply(Math, j) < len
          next = [];
          for (var i = 0; i < k.length; i++) {
            if (next.indexOf(Number(k[i])) == -1 
              && idx.indexOf(Number(k[i])) !== -1) {
                next.push(Number(k[i]))
            }
          }
          if (prev < n && next.length === len - 1 
             || next.length === len && prev > curr) {
               res[res.length] = next.length < len 
                                 ? [0].concat.apply([0], next) 
                                 : next;
          }
        }
      }
      curr = prev;
    }
  };
  return res.map(function(value) {
    return value.map(function(index) {
      return arr[index]
    })
  })
}

getNextLexicographicPermutation(arr);

作为数组arr的索引的数字之间的数字差异图将是

// graph 3
// reflecting input `abcd`
[9,81,18,81,9,702,9,171,27,72,18,693,18,72,27,171,9,702,9,81,18,81,9]

// version 2.0 without using `RegExp`

function getNextLexicographicPermutation(arr) {
  var len = arr.length;
  var idx = arr.map(function(_, index) {
    return index
  });
  var n = Math.pow(10, len - 1);
  var p = 9;
  var last = Number(idx.slice().reverse().join(""));
  var res = [];
  var curr = Number(idx.join(""));
  var prev, k, next;

  while (curr <= last) {
    prev = curr;            
    k = String(prev).split("").map(Number);
    if (k.every(function(v) {
        return idx.indexOf(v) !== -1
      }) && k.length >= len - 1) {
      next = [];
      for (var i = 0; i < k.length; i++) {
        if (next.indexOf(Number(k[i])) == -1 
          && idx.indexOf(Number(k[i])) !== -1) {
            next.push(Number(k[i]))
        }
      }
      if (prev < n && next.length === len - 1 
          || prev > n && next.length === len)) {
        res[res.length] = next.length < len 
                          ? [0].concat.apply([0], next) 
                          : next;
      }
    }
    prev += p;
    curr = prev;
  };
  return res.map(function(item) {
    return item.map(function(v) {
      return arr[v]
    })
  })
}
getNextLexicographicPermutation(arr)

第二个版本的效率比第一个版本大大提高了,在Most efficient method to check for range of numbers within number without duplicates通过@lleaff的答案替换了RegExp的位掩码。

DevTools版本和位掩码版本之间由RegExp生成的相关配置文件应该可以在Chrome浏览器中重新编写,但是由于忽略了对所执行的确切测试的评论,因此无法精确地再现数字和发布的时间没有花费更多的时间来验证上一个问题中发布的数字。虽然输入集的.length为10,但浏览器选项卡可能已崩溃,因此无法准确记住。重要的是,位掩码测试版本比RegExp测试版本更有效。

// version 2.1, substituting bitmask for `RegExp`

function getNextLexicographicPermutation(arr) {
  function checkDigits(min, max, n) {
    var digits = 0;
    while (n) {
      d = (n % 10);
      n = n / 10 >> 0;
      if (d < min || d > max || (digits & (1 << d)))
        return false;
      else
        digits |= 1 << d;
    }
    return true;
  }
  var len = arr.length,
    idx = arr.map(function(_, index) {
      return index
    }),
    p = 9,
    min = 0,
    max = len - 1,
    last = Number(idx.slice().reverse().join("")),
    res = [],
    curr = Number(idx.join("")),
    next;

  while (curr < last) {
    next = (curr += p);
    if (checkDigits(min, max, next)) res[res.length] = next;
    curr = next;
  };

  return res.map(function(item) {
    var item = String(item).split("").map(Number);
    item = item.length < arr.length ? [0].concat(item) : item;
    return item.map(function(index) {
      return arr[index]
    }).filter(Boolean)
  })
}    
getNextLexicographicPermutation(arr);

这些笔记和流程在一年多的时间里花了一年多的时间来展示和证明。主要是考虑如何同时使用倾斜斜率指数来获取斜率两侧的指标,而不是编码算法。

大部分数学试图在9号的相邻倍数之间得出进一步的相关性,因为能够计算9号的确切下一个倍数

9 

用于将数字递增9,然后从结果数字中过滤重复值。我还没有能够解释倾斜斜率上相邻的九个倍数之间的相互关系到乘法或除法可以代替加法和排除的程度。

决定最终为无限输入集生成无限多个排列的命题创建概念证明,仅使用倾斜斜率,或仅使用索引作为数字,可能排列的前半部分加上一个。 / p>

// version 3, generate second half of permutations using indexes of first 
// half of permutations

function getNextLexicographicPermutation(arr) {

    for (var l = 1, i = k = arr.length; l < i ; k *= l++);

    function checkDigits(min, max, n) {
        var digits = 0;
        while (n) {
            d = (n % 10);
            n = n / 10 >> 0;
            if (d < min || d > max || (digits & (1 << d)))
                return false;
            else
                digits |= 1 << d;
        }
        return true;
    }

    var len = arr.length
    , idx = arr.map(function(_, index) {
        return index
    })
    , p = 9
    , min = 0
    , max = len - 1
    , last = Number(idx.slice().reverse().join(""))
    , curr = Number(idx.join(""))
    , res = []
    , diff = []
    , result = []
    , next;

    while (res.length < (k / 2) + 1) {
        next = (curr += p);
        if (checkDigits(min, max, next)) res[res.length] = next;      
        curr = next;
    };

    for (var i = 0; i < res.length; i++) {
      var item = res[i];
      item = String(item).split("").map(Number);
      item = (item.length < arr.length ? [0].concat(item) : item)
             .map(function(index) {
                return arr[index]
             }).filter(Boolean);
      result.push(item)
    }

    res.reduce(function(a, b) {
      diff.push(b - a);
      return b
    });

    for (var i = 0, curr = res[res.length - 1], n = diff.length - 2
        ; result.length < k;  i++, n--) {
          curr = curr + diff[n];
          result.push(
            String(curr).split("")
            .map(function(index) {
                return arr[index]
            })
          );
    }
    return result;
}
getNextLexicographicPermutation(arr);

算法开发的另一个最终步骤是给定一个任意.length输入,以便能够计算索引,从而计算出数组的 nth 排列值;通过使用乘法,除法,代数,三角函数或微积分的单一公式。

请在答案中包含可重复的基准。原因是我无法准确记住如何在Profiles得出DevTools的数字,但是如果记得在开始时正确使用了console.time()console.timeEnd()console.profile()和使用的各个部分的结尾。备份你的实验;你永远不知道硬盘或操作系统是否或何时会崩溃。您通常可以从磁盘检索数据,但需要花费时间和精力。以同样的方式保存测试,您也可以保存算法版本,以便自我复制和重现其他人。原始测试的全部范围都丢失了。

在浏览器选项卡崩溃之前可以导出多少排列的测试的幸存残余只是从不同操作系统检索的注释

// 362880 876543210 876543219 
var arr = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];

如果准确地重新收集,当"j"被添加到数组时,铬的活动标签崩溃了。第一个数字是集合的排列总数&#34; a&#34;通过&#34;我&#34 ;;接下来的两个数字可能,但不确定,是今天组成的版本3之前的一个版本的两个测试的结果。

这是现在在stackoverflow.com上发布上述披露的另一个原因,为了保留算法的原理并对目前完成的代码进行处理,减少一些灾难会破坏所有原始笔记和工作;例如,连续几天醒来试图解释数字之间的模式和关系;忽略了记录试图将算法移植到代码中的注释代码的所有特定测试模式;或者@JaromandaX描述了情况"PEBCAK"

新鲜的眼睛可能从不同的角度看待算法效率。

我们可以重现上面某些保留代码版本的结果图表,例如使用console.time()console.timeEnd()performance.now()或其他适当的测试,包括完成函数的时间,可以复制。

// console.time("version N");
// getNextLexicographicPermutation(arr);
// console.timeEnd("version N");

var tests = [];

for (var i = 0; i < 10000; i++) {
  var from = window.performance.now();
  getNextLexicographicPermutation(arr);
  tests.push(window.performance.now() - from);
}

for (var i = 0, k = 0; i < tests.length; i++) {
  k += tests[i];
}

var avg = k/tests.length;

// version 1 `avg`: 0.2989265000001993
// version 2.0 `avg`: 0.8271295000007376
// version 2.1 `avg`: 0.17173000000003666
// version 3 `avg`: 0.12989749999987543

作为脚注,我将提到我使用算法的原理来导出Fastest way to generate all binary strings of size n into a boolean array?处的预期结果,其中,数字9在倾斜斜率内显示为关键数字,并且匹配角度赤纬也是可观察到的。虽然没有对该问题所描述的特定形式的输入和结果进行进一步的测试和自动化。答案是关于忽略值的方法的可行性的概念证明,并且仅使用应用于单个数字初始数零的波状递增模式来导出集合的无限排列。

问题:

  1. 如何提高算法的效率;都 计算和数学?

  2. 我们可以同时为倾斜和倾斜斜率创建指标;而不是在计算倾斜斜率后确定赤纬斜率?

    1. 索引之间是否存在数学关系或模式,例如,图1 图2 中的数字集合或图3 ,源自任意四个值的输入,例如

      ABCD

    2. ["a", "b", "c", "d"]
      

      作者还没有认出来;这可以用来 进一步减少当前实现的计算次数 得出结果?

4 个答案:

答案 0 :(得分:15)

TL; DR版

不幸的是,提高此算法性能的唯一方法是摆脱它并使用更好的东西。

&#34;真相&#34;显而易见?(剧透警报:是的,他们是)

在你的长篇文章中,我看到了两个真理&#34;你发现了:

  1. 如果您将数组索引编写为字符串,然后将这些字符串重新解释为两个连续排列的数字,则差异将是9的乘法

  2. &#34;斜坡图&#34;是对称的

  3. 不幸的是,这些事实都很明显,其中一个甚至都不是真的。

    第一个事实是正确的,只要数组的长度小于10.如果它大于10,即某些索引是&#34;映射&#34;到2个字符,它不再是真的。如果你知道一个divisibility rule for 9(在十进制系统中),这显然是正确的:数字之和应该是9的乘数。显然,如果两个数字具有相同的数字,则它们具有相同的提醒模块9,因此它们是差异是9的乘法。此外,如果您在任何系统中解释您的字符串,其基数大于数组的长度,则差异将是base - 1的乘法,原因相同。例如,让我们使用基于8的系统(列是:置换,置换索引,索引字符串,索引字符串从8基换算为十进制,差异):

    abc 0 012 10  
    acb 1 021 17   7
    bac 2 102 66  49
    bca 3 120 80  14
    cab 4 201 129 49
    cba 5 210 136  7
    

    如果您始终使用的系统大于数组的长度,那么这个事实就是正确的(但您可能需要提出新的数字)

    第二个陈述也是显而易见的,并且是&#34;词典顺序&#34;的直接后果。被定义为。对于每个索引i,如果我对第一个i-th排列和最后i-th排列的索引数组求和,则总和将始终相同:所有值等于长度的数组数组。例如:

    1. abc 012 - cba 210 => 012 + 210 = 222  
    2. acb 021 - cab 201 => 021 + 201 = 222
    3. bac 102 - bca 120 => 102 + 120 = 222
    

    如果考虑一系列负指数(即[-N, -(N-1), ..., -1, 0])的排列,这很容易看出。显然,从这个数组开始的i-th排列与i-th的{​​{1}}排列结尾相同,仅仅是否定符号。

    其他问题

      
        
    1. 索引之间是否存在数学关系或模式作为数字,例如,图1,图2或图3中的数字集,从任意四个值的输入派生,例如
    2.   

    是的,有。实际上,这正是您在问题Permutations without recursive function call中链接的答案首先起作用的原因。但我怀疑有一种算法比该答案中提供的算法效率更高。实际上,该答案试图将所请求的排列的位置转换为变量 - 基数值系统中的值,其中基数范围从1到数组的长度。 (对于一个更广泛的变基数值系统的例子,考虑如何将毫秒转换为天 - 小时 - 分 - 秒 - 毫秒。你有效地使用数字系统,基数1000-60-60-24-unlimited。所以当你看到[0, 1, 2, ... N]您将其转换为毫秒为12345 days 8 hours 58 minutes 15 seconds 246 milliseconds,即您将该表示法视为((((12345 * 24 + 8) * 60) + 58 * 60) + 15) * 1000 + 246)。

    通过排列,您可能需要执行两项不同的任务:

    1. 生成12345 (no base/unlimited) days 8 (24-base) hours 58 (60 base) minutes 15 (60 base) seconds 246 (1000-base) milliseconds排列。您在SO答案中链接的算法相当有效。我怀疑还有什么更好的

    2. 为给定的一个生成所有排列或排列流或下一个排列。这似乎是您尝试使用代码的那个。在这种情况下,分析给定排列的简单算法,找到排列未排序的第一个位置,并且开关+排序是否合理有效(这是拖延者似乎实现的但我没有查看细节)。我再次怀疑还有更好的东西。关于基于数字的算法不会更有效的主要障碍是因为它在一般情况下工作(即长度> = 10),你需要在大基数和那些操作中使用长算术进行除法不再是O(1)了。

    3. 更新(回复评论)

        
          

      我所声称的是,没有办法计算比直接计算排列序列更有效的数字序列。

        
           

      我不同意这个主张。你能证明并证明这种说法吗?

      不,我甚至不知道如何以正式的方式陈述这种说法(如何定义不计算数字序列的算法类别?)。我仍然有一些证据支持这一点。

      首先,你可能不是已知宇宙中最聪明的人,这是一个相对古老而众所周知的话题。因此,您发现一种比现有算法快得多的算法的可能性很低。事实上,没有人使用这种技术是一种反对你的证据。

      另一点不那么随意:我在#2建议的用于生成序列中所有排列的算法实际上相当有效,因此很难被击败。

      考虑寻找下一个排列的一些步骤。首先,您需要找到订单不下降的末端的第一个位置。假设它是i-th。需要k次比较才能找到它。然后你需要进行一次交换和排序。但如果你有点聪明,你可能会注意到&#34;排序&#34;这里可能会更快,因为列表已经排序(但顺序相反)。因此,这里的排序与找到k - 元素的位置相反。考虑到数组已排序,您可以使用k复杂度的二进制搜索。因此,您需要在内存中移动O(log(k))个元素,而不是k+1个比较。这是一些代码:

      k

      现在假设您使用基于数字的方法做同样的事情,并假设您可以立即计算为每个步骤添加的数字。那你现在用多少时间?由于您必须使用长算术,并且我们知道您添加的最高位数为// generates array of all permutatations of array of integers [0, 1, .., n-1] function permutations(n) { // custom version that relies on a fact that all values are unique i.e. there will be no equality var binarySearch = function (tgt, arr, start, end) { // on small ranges direct loop might be more efficient than actual binary search var SMALL_THRESHOLD = 5; if (start - end < SMALL_THRESHOLD) { for (var i = start; i <= end; i++) { if (arr[i] > tgt) return i; } throw new Error("Impossible"); } else { var left = start; var right = end; while (left < right) { var middle = (left + right) >> 1; //safe /2 var middleV = arr[middle]; if (middleV < tgt) { left = middle + 1; } else { right = middle; } } return left; } }; var state = []; var allPerms = []; var i, swapPos, swapTgtPos, half, tmp; for (i = 0; i < n; i++) state [i] = i //console.log(JSON.stringify(state)); allPerms.push(state.slice()); // enfroce copy if (n > 1) { while (true) { for (swapPos = n - 2; swapPos >= 0; swapPos--) { if (state[swapPos] < state[swapPos + 1]) break; } if (swapPos < 0) // we reached the end break; // reverse end of the array half = (n - swapPos) >> 1; // safe /2 for (i = 1; i < half + 1; i++) { //swap [swapPos + i] <-> [n - i] tmp = state[n - i]; state[n - i] = state[swapPos + i]; state[swapPos + i] = tmp; } // do the final swap swapTgtPos = binarySearch(state[swapPos], state, swapPos + 1, n - 1); tmp = state[swapTgtPos]; state[swapTgtPos] = state[swapPos]; state[swapPos] = tmp; //console.log(JSON.stringify(state)); allPerms.push(state.slice()); // enfroce copy } } //console.log("n = " + n + " count = " + allPerms.length); return allPerms; } ,因此您需要至少执行k次添加和{{1}溢出的比较。当然,你仍然必须至少k写入内存。所以要比上面描述的更有效&#34;通常&#34;算法,您需要一种方法来计算k - 数字长数(您将添加的数字),其时间少于在大小为k的数组中执行二进制搜索。这对我来说是一项非常艰巨的工作。例如,仅使用相应的系数乘以9(或更确切地说k)可能需要更长的时间来使用长算术。

      那么你有什么其他机会?根本不使用长算术。在这种情况下,第一个明显的论点是数学上比较小k上的算法性能没有意义(这就是为什么Big-O notation用于算法复杂性)。仍然可能有必要争取一个小小的&#34;来自纯数学&#39;观点但是&#34;大&#34;对于真实世界的情况,范围可达20个元素阵列的排列,这些元素仍然适合长(64位)整数。那么你可以通过不使用长算术获得什么?那么你的加法和乘法只需要一条CPU指令。但是,您必须使用除法将您的号码拆分回数字,并且每个步骤需要N-1个分区和N检查(即比较)。并且N总是大于N,通常更多。因此,这似乎也不是改善绩效的好方法。

      总结:建议的算法是有效的,任何基于算术的算法在算术部分可能效率较低。

答案 1 :(得分:2)

解决工具箱中有许多不同工具的问题时会有所帮助。适用于此问题的工具是The On-Line Encyclopedia of Integer Sequences®(OEIS®)。

问题中指出的是序列

9,81,18,81,9,702,9,171,27,72,18,693,18,72,27,171,9,702,9,81,18,81,9

可以通过采用1,2,3,4的字典排列并将其转换为数字来生成

1234,1243,1324,1342,1423,1432,2134,2143,2314,2341,2413,2431,    
3124,3142,3214,3241,3412,3421,4123,4132,4213,4231,4312,4321

然后从其前任中减去一个排列,例如

1243 - 1234 = 9
1324 - 1243 = 81
1342 - 1324 = 18
...

现在OP注意到所有差值都可以被9整除,所以除以9得

1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2, 77, 2, 8, 3, 19, 1, 78, 1, 9, 2, 9, 1

并使用OEIS search给出

A217626 - ...... 123 ... m的排列的第一个差异除以9(m足够大,m!> n)。

关于OP问题

  

导出需要的精确数的数学公式   被添加或乘以9到当前整数   表示当前排列的索引以获得下一个   下一个词典的索引的整数表示   排列。

在链接部分是

  

R上。 J. Cano,关于此序列的其他信息。

并点击Additional information about this sequence

打开一个页面,讨论序列的对称性

  

{1,9,2,9,1,78,1,19,3,8,2,77,2,8,3,19,1,78,1,   9,2,9,1}

     

这些元素是第4个!这个序列的术语。对称性   这种情况的中心(N = 4的排列)是第12个任期,因为   通过删除它和从该序列开始的零,只留下一个偶数   根据其抵消重复一次的术语数量   相对于对称中心,如下所示   变换:

     

0){0,1,9,2,9,1,78,1,19,3,8,2,       77,2,8,3,19,1,78,1,9,2,9,1};删除零,

     

1){1,9,2,9,1,78,1,19,3,8,2,77,           2,8,3,19,1,78,1,9,2,9,1};分成三部分,其中一部分是对称中心,

     

2){1,9,2,9,1,78,1,19,3,8,2}; {77}; {2,8,3,   19,1,78,1,9,2,9,1};删除对称中心,

     

3){1,9,2,9,1,78,1,19,3,8,2}; {2,8,3,19,1,78,1,   9,2,9,1};反映其中一件,

     

4){1,9,2,9,1,78,1,19,3,8,2}; {1,9,2,9,1,78,1,   19,3,8,2};在这一步之后,一种新的中心变得明显。

     

由于所有可能的排列集都有一些共同的术语   从这个序列,我们可能期望再次找到这些“中心”   开发另一个案例。

关于代码/算法,链接部分还有另一个参考

R上。 J. Cano,Template for a base-independent sequencer in C

/*
 * ########################################## 
 * # Base independent sequencer for A217626 #
 * ##########################################
 * 
 * This program is free software.
 * Written by R. J. Cano (remy at ula.ve, Or reemmmyyyy at gmail.com)
 * On Jan 9 2014, for educational purposes and released under 
 * the terms of the General Public License 3.0 (GNU-GPL 3.0);
 * 
 * There is NO warranty not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * Note: For a large set of terms (>10!-1) the present program might be prone to data type overflows.
 * 
*/

#include <stdio.h>
#include <stdlib.h>

long _base= 10;
long _showOffset= 1;
/** Standard output field width. An aid for comparisons using MD5 checksums. **/
long _normWidth= 13; 
/** Set this to 0 for print everything in a single line. **/
long _onePerLine= 1;
/** 0 for the vector representation, 1 for the integer representation **/
long _objectToShow= 1;

long permute(long*, long*, long);
long vec2polyEval(long*, long, long);

int main(int argc, char *argv[]) {
  long v1[100],v2[100],v3[100],u[100],n,k,l,offset=0;
  _showOffset*= _onePerLine;
  /* The size of the output (n!-1 items) is no longer read from the standard input. 
     scanf("%li",&n); -- Does stop silently, therefore it is avoided. */
  n= strtol(argv[1], NULL, _base); /* Direct conversion from the command line parameter(s) */
  for(k=0; k<100; k++) {
    v1[k]= (k<n)*(k);
    v2[k]= v1[k];
    v3[k]= 0;
  }
  while(permute(v2,u,n)) {
    for(k=0;k<n-1;k++) {
      v3[k+1]=0;
      for(l=k+1;l<n;l++) {
    v3[k+1]+=(u[l]-v2[l]);
      }
    }
    if (_showOffset) printf("%li ", ++offset);
    if (!_onePerLine) printf(",");
    if (!_objectToShow) {
      for(k=0;k+n<_normWidth;k++) { printf(",0"); }
      for(k=0;k<n;k++) { printf(",%li",v3[k]); }
      printf(";");
    } else {
      printf("%li", vec2polyEval(v3,_base,n));
    }
    if (_onePerLine) printf("\n");
  }
  if (!_onePerLine) printf("\n");
  return EXIT_SUCCESS;
}

long permute(long *data, long *previous, long Size) {
  long tau, rho=Size-1, phi=Size-1;
  for (tau=0;tau<Size;tau++) previous[tau]= data[tau];
  while((rho > 0)&&(data[rho]<= data[rho-1])) rho--;
  rho--;
  if(rho<0) return 0;
  while((phi > rho)&&(data[phi]<=data[rho])) phi--;
  tau= data[rho];
  data[rho]= data[phi];
  data[phi]= tau;
  Size--;
  rho++;
  while(Size>rho) {
    tau= data[Size];
    data[Size]= data[rho];
    data[rho]= tau;
    Size--;
    rho++;
  }
  return 1;
}

long vec2polyEval(long* v, long B, long m) {
  long ans=0, pow=1, k;
  for(k=m-1;k>=0;k--) {
    ans+= v[k]*pow;
    pow*= B;
  }
  return ans;
}

实施例

要运行我使用的Visual Studio Community 2015 C代码,它是免费的,并构建为Win32控制台项目。


C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 2
1 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 3
1 1
2 9
3 2
4 9
5 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 4
1 1
2 9
3 2
4 9
5 1
6 78
7 1
8 19
9 3
10 8
11 2
12 77
13 2
14 8
15 3
16 19
17 1
18 78
19 1
20 9
21 2
22 9
23 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 5
1 1
2 9
3 2
4 9
5 1
6 78
7 1
8 19
9 3
10 8
11 2
12 77
13 2
14 8
15 3
16 19
17 1
18 78
19 1
20 9
21 2
22 9
23 1
24 657
25 1
26 9
27 2
28 9
29 1
30 178
31 1
32 29
33 4
34 7
35 3
36 66
37 2
38 18
39 4
40 18
41 2
42 67
43 1
44 19
45 3
46 8
47 2
48 646
49 1
50 19
51 3
52 8
53 2
54 67
55 1
56 29
57 4
58 7
59 3
60 176
61 3
62 7
63 4
64 29
65 1
66 67
67 2
68 8
69 3
70 19
71 1
72 646
73 2
74 8
75 3
76 19
77 1
78 67
79 2
80 18
81 4
82 18
83 2
84 66
85 3
86 7
87 4
88 29
89 1
90 178
91 1
92 9
93 2
94 9
95 1
96 657
97 1
98 9
99 2
100 9
101 1
102 78
103 1
104 19
105 3
106 8
107 2
108 77
109 2
110 8
111 3
112 19
113 1
114 78
115 1
116 9
117 2
118 9
119 1

使用10的测试,可以降低一些算法很好地工作。


C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 10
1 1
2 9
3 2
4 9
5 1
6 78
7 1
8 19
9 3
10 8
...
3628790 8
3628791 3
3628792 19
3628793 1
3628794 78
3628795 1
3628796 9
3628797 2
3628798 9
3628799 1

注意:答案数量为X!-110! = 3628800有一个小于阶乘,因为这是计算差异。

我没有把它转换成JavaScript,因为你的JavaScript效率可能比我现在好。目前我专注于Prolog和F#。

补编

如果您正在访问此问题以了解如何在计算机上枚举排列,那么您应阅读的两篇特定论文

Robert Sedgewick的

Permutation Generation Methods

“计算机程序设计的艺术”Donald E. Knuth撰写的第7.2.1.2节Generating all permutations草案。

和书

理查德·斯坦利

Enumerative Combinatorics

答案 2 :(得分:0)

缺乏数学和英语技能使我很难详细阐述这样一个问题: - |虽然我想玩排列,但我做了一些不那么偏离主题的事情。请参阅https://stackoverflow.com/a/43302308/1636522的TL; DR,我有同样的感觉,稍后我会尝试证明我的算法的有效性,我需要一点时间来考虑它。

var a = "aceg".split(""); 
do {
  console.log(a.join(""));
} while (a = next(a));

function next (a) {
  var i, c, head, next;
  var b = a.slice();
  var n = b.length;
  for (i = 1; i < n; i++) {
    if (b[n - i] > b[n - (i + 1)]) {
      head = n - (i + 1);
      next = n - i;
      for (i = next + 1; i < n; i++) {
        if (b[i] < b[next] && b[i] > b[head]) {
          next = i;
        }
      }
      c = b[head];
      b[head] = b[next];
      b[next] = c;
      return b.slice(0, head + 1).concat(
        b.slice(head + 1).sort()
      );
    }
  }
}

答案 3 :(得分:0)

购买更多内存。我无法完全遵循您的算法,但在我看来,由于执行的操作是确定性的,因此我建议您创建一个“预处理”的数据结构,它基本上将逻辑子集减少到可重用的查找表。

// reusableTable
abc 012 0
acb 021 1 9
bac 102 2 81
bca 120 3 18
cab 201 4 81
cba 210 5 9

其中可查找映射将是

// mapping
0 a
1 b
2 c

并解决你要进行嵌套数组查找的数字(假设0指向左/最大的数字),如下所示:

sum[i] = mapping[reusableTable[i][0]] * 100 + mapping[reusableTable[i][1]] * 10 + mapping[reusableTable[i][2]];

或者更确切地说是硬编码的功能

diff[1] = (mapping[reusableTable[1][1]] - mapping[reusableTable[0][1]]) * 10 + (mapping[reusableTable[1][2]] - mapping[reusableTable[0][2]]);
diff[2] = (mapping[reusableTable[2][0]] - mapping[reusableTable[1][0]]) * 100 + (mapping[reusableTable[2][1]] - mapping[reusableTable[1][1]]) * 10 + (mapping[reusableTable[2][2]] - mapping[reusableTable[2][2]]); 
diff[3] = ... 

由于实际原因,如内存使用,预处理表的大小是有限的,但对我而言,通过一些努力,您应该能够“分而治之”以优化更长序列的运行。

编辑:

以上diff [2]的解码:

diff[2] = (1 - 0) * 100 + (0 - 2) * 10 + (2 - 1); 

这是81.

基本上,上面试图取消for循环并尽可能地取代'硬编码'可重用函数。这应该提高算法的效率。

当数字的数量大于预先计算的表格时,应该可以重复例外情况,或者采用我认为已经在问题中提出的方法的for循环;只有通过使用优化函数,增量/移位为n而不是1。