Algorithm 如何计算线性时间内的置换,有一个扭曲

Algorithm 如何计算线性时间内的置换,有一个扭曲,algorithm,permutation,Algorithm,Permutation,我在Java中遇到了一个资源调度问题,需要对事物进行排序,但对哪些资源可以彼此相邻有限制。一个很好的类比是一串“数字”,其中只有某些数字可以相邻。我的解决方案是递归的,适用于小字符串,但运行时是O(X^N),其中X是可能的位数(基数),N是字符串的长度。它很快变得难以管理 使用下面的兼容性矩阵,这里有几个允许字符串的示例 长度为1:0,1,2,3,4 长度为2:02、03、14、20、30、41 长度为3:020030141202203302303414 0 1 2 3 4 ---

我在Java中遇到了一个资源调度问题,需要对事物进行排序,但对哪些资源可以彼此相邻有限制。一个很好的类比是一串“数字”,其中只有某些数字可以相邻。我的解决方案是递归的,适用于小字符串,但运行时是O(X^N),其中X是可能的位数(基数),N是字符串的长度。它很快变得难以管理

使用下面的兼容性矩阵,这里有几个允许字符串的示例
长度为1:0,1,2,3,4
长度为2:02、03、14、20、30、41
长度为3:020030141202203302303414

0 1 2 3 4 --------------------- 0| 0 0 1 1 0 1| 0 0 0 0 1 2| 1 0 0 0 0 3| 1 0 0 0 0 4| 0 1 0 0 0 0 1 2 3 4 --------------------- 0| 0 0 1 1 0 1| 0 0 0 0 1 2| 1 0 0 0 0 3| 1 0 0 0 0 4| 0 1 0 0 0 我计算所有长度为N的字符串的解决方案是从一个空字符串开始,排列第一个数字,然后对所有长度为N-1的字符串进行递归调用。递归调用检查添加的最后一个数字,并尝试该数字旁边的所有排列。有一些优化,所以我不会每次都尝试排列00、01、04,例如,仅排列02、03,但性能仍然很差,因为它从基5(示例)扩展到基4000


除了尝试列举所有排列之外,还有什么更好的方法可以计算排列吗?

也许我不明白这一点,但如果有一个列表表,每个数字后面都有一个有效数字列表,这难道不是很好吗

然后,您要生成的例程将获取累积结果、数字和当前数字。比如:

// not really Java - and you probably don't want chars, but you'll fix it
void GenerateDigits(char[] result, int currIndex, char currDigit)
{
    if (currIndex == kMaxIndex) {
        NotifyComplete(result);
        return;
    }
    char[] validFollows = GetValidFollows(currDigit); // table lookup
    foreach (char c in validFollows) {
        result[currIndex] = c;
        GenerateDigits(result, currIndex+1, c);
    }
}

复杂度随着要生成的位数的增加而增加,但该函数取决于任何一个位数的有效跟随总数。如果每个数字的跟随总数相同,比如说,k,那么生成所有可能排列的时间将是O(k^n),其中n是位数。对不起,我不能改数学。以10为基数生成n个数字的时间是10^n。

您是否只想知道使用给定矩阵中的规则可以生成多少给定长度的字符串?如果是这样,这样的方法应该有效:

n = 5
maxlen = 100

combine = [
      [0, 0, 1, 1, 0],
      [0, 0, 0, 0, 1],
      [1, 0, 0, 0, 0],
      [1, 0, 0, 0, 0],
      [0, 1, 0, 0, 0]
   ]

# counts of strings starting with 0,1,...,4, initially for strings of length one:
counts = [1, 1, 1, 1, 1]

for size in range(2, maxlen+1):
   # calculate counts for size from count for (size-1)
   newcount = []
   for next in range(n):
      total = 0
      for head in range(n):
         if combine[next][head]:
            # |next| can be before |head|, so add the counts for |head|
            total += counts[head]
      # append, so that newcount[next] == total
      newcount.append(total)
   counts = newcount
   print "length %i: %i items" % (size, sum(counts))

你的算法似乎是最优的

你是如何使用这些排列的?您是将它们累积在一个列表中,还是逐个使用?因为有大量的这种排列,所以性能差可能是由于内存使用量大(如果您收集了所有排列),或者只是花费了太多时间。你不能在平凡的时间里做数十亿次循环

回复评论:

如果您只想计算它们,则可以使用动态规划:

设count[n][m]是一个数组,其中count[l][j]是长度为l且以j结尾的置换数

然后count[l][i]=count[l-1][i1]+count[l-1][i2]+…,其中i1,i2。。。是i前面的数字(可以保存在预先计算的数组中)


每个计数单元都可以通过对K个数求和(K取决于兼容矩阵)来填充,因此复杂性为O(KMN),M是置换的长度,N是总位数。

我不太确定你在问什么,但因为可能有N!如果是n个数字串的排列,则列出它们的速度将无法超过n!。我不确定你认为你是如何得到O(n^2)的运行时间的

如果您只需要特定长度的字符串数量,您可以将兼容矩阵自身相乘几次,然后将其值相加

n=字符串长度
A=兼容性矩阵
可能的字符串数=An-1的和

举几个例子:

n = 1
| 1 0 0 0 0 |
| 0 1 0 0 0 |
| 0 0 1 0 0 |
| 0 0 0 1 0 |
| 0 0 0 0 1 |
sum: 5

n = 3
| 2 0 0 0 0 |
| 0 1 0 0 0 |
| 0 0 1 1 0 |
| 0 0 1 1 0 |
| 0 0 0 0 1 |
sum: 8

n = 8
| 0 0 8 8 0 |
| 0 0 0 0 1 |
| 8 0 0 0 0 |
| 8 0 0 0 0 |
| 0 1 0 0 0 |
sum: 34
原始矩阵(第i行,第j列)可以被认为是以符号i开头的字符串数量,其下一个符号是符号j。或者,您可以将其视为长度为2的字符串的数量,这些字符串以符号i开头,以符号j结尾

矩阵乘法保留此不变量,因此在求幂之后,An-1将包含以符号i开头、长度为n、以符号j结尾的字符串数

有关更快计算矩阵幂的算法,请参阅

(谢谢stefan.ciobaca)

这种特殊情况归结为以下公式:

可能的字符串数=f(n)=4+∑k=1..n2⌊k-1⁄2⌋ = f(n-1)+2⌊n-1⁄2⌋


谢谢您的建议已经作为我提到的优化实现,我只查看当前数字后面的数字。从O(n^2)开始改进运行时间,但当我们达到数千位数时,指数级仍然会崩溃。我不认为他提出了建议。他试图理解你的算法。你使用了太多的单词“线性”、“指数”、“O(N^2)”,而不理解它们的含义。什么定义元素是否可接受?兼容性矩阵是算法的输入吗?澄清一下:您只对计算置换总数感兴趣吗?该矩阵是前一个函数的结果。上面显示的长度为1、2和3的算法是我用来测试其他算法的。F(1)=5,F(2)=6,F(3)=8,是-只计算,不枚举。老兄,你不懂O(.)符号。您描述的算法是指数O(5^N),而不是O(N^2)。请参阅下面的一个非常快速的解决方案:现在调整计数-不要使用它们。就迭代/递归解决方案而言,我认为我的解决方案相当不错,但正如您所说,您无法在合理的时间内完成其中的4000^10个,因此我正在尝试寻找替代方案。谢谢。我认为这会起作用,但我相信这和我的问题是一样的。。。N个元素上的嵌套循环/递归。如果我遗漏了什么,请纠正我。你遗漏了一些
n       f(n)
----    ----
   1       5
   2       6
   3       8
   4      10
   5      14
   6      18
   7      26
   8      34
   9      50
  10      66