算法:最小化播放列表而不改变播放顺序

5

我正在寻找一种算法来缩减一个有序但不唯一的列表(播放列表)。

我尝试过集合理论,但还没有找到合适的内容。

例子:

[a, b, b, c] -> [a, b, b, c] Cannot be reduced. 
[a, b, b, a, b, b] -> [a, b, b]. 
[b, b, b, b, b] -> [b]. 
[b, b, b, b, a] -> [b, b, b, b, a] Cannot be reduced. 

想要获取所有现有的子列表并计算每个实例。 如果存在这样一个子列表,其中计数乘以子列表长度等于原始列表,则取最短的符合此条件的子列表。

这似乎有点粗暴,一定有更简单/更快的解决方案。


你对 [a,b,b,c][b,b,b,b,a] 的不可约性有什么规则?这个规则的直觉是什么?我问这个问题是因为它看起来像一个更一般的算法的特殊情况。根据你的第三条规则,[b,b] 将(递归地)简化为 [b][b,b,b,b] -> [b] 也将如此简化。 - I GIVE CRAP ANSWERS
你所寻找的是一种数据压缩算法。请查看哈夫曼编码和算术编码。 - Justin Peel
[a,b,b,c] -> [a,b,b,c] [b,b,b,b,a] -> [b,b,b,b,a] [b, b] -> [b] 所有这些情况下,原始播放列表都被保留,因为它们永远循环。 - kiteloop
4个回答

3

首先,您无需检查所有子列表--仅需要检查长度为完整列表长度的因子

如果您主要关心的是编码简单性而不是原始速度,只需让正则表达式引擎解决问题:

/^(.+?)\1+$/

这是一个变体,基于 Abigail的神奇Perl正则表达式查找质数


编码简单易懂,尤其是看起来像这样的。但我有两个问题。我不理解它,也无法让它工作 :) var str = "abcabc"; var regex = new Regex(@"/^(.+?)\1+$/"); var match = regex.Match(str); 这并没有产生任何结果。还尝试了 perl -e 'print /^(.+?)\1+$/' abcabc,但没有结果。 - kiteloop
而且你可能会注意到,stackoverflow的格式不起作用,对此我很抱歉。 - kiteloop

2
对于每个小于或等于列表长度的n(其中N是列表的长度),如果n是N的因数,那么检查重复第一个n个字符的子列表是否生成原始列表。如果是,则找到了一个潜在的答案(答案是最短的)。这样可以将复杂度降低到小于O(N ^ 2),但在最坏情况下与暴力搜索具有相同的效率。
你可以通过注意到,如果例如长度为2的子列表成功生成了前4个字符但未生成完整列表,则长度为4的子列表将失败来进行修剪。你可以保留所有这些子列表长度的列表以不予检查,从而减少一些计算。

有趣,明天我会尝试编写一些代码,看看结果是否令人满意。 - kiteloop

0

使用质数对每个集合元素进行编码。

例如:

a -> 2
b -> 3
c -> 5

现在,您需要再维护两个列表。

第一个列表是质数,第二个列表是它们的指数。

思路是:当您遇到一个元素时,记录它的质数和连续出现的次数。

对于[a, b, b, c],您会得到这个:

[2, 3, 3, 5]

这可以被记录为:

[2, 3^2, 5]

或者,更准确地说:

[2^1, 3^2, 5^1]

而且你维护两个列表:

[2,3,5]  // primes in succession - list [p]
[1,2,1]  // exponents - list [e]

现在,从两个列表的末尾到中间进行迭代,通过检查第一个元素 [p]^[e] 是否与最后一个元素相同来进行比较; 如果它们都相同,则依次检查第二个元素和倒数第二个元素等等... 如果它们全部都相同,则可以缩小列表。
在这个例子中,您要检查是否 2^1*5^1 == 3^2*3^2; 由于它们不相同,因此无法缩小。
让我们尝试一下[a, b, b, a, b, b]
这是编码为
[2^1, 3^2, 2^1, 3^2]

或者,

[2, 3, 2, 3]  // primes
[1, 2, 1, 2]  // exponents

现在,我们检查是否2^1 * 3^2 == 3^2 * 2^1(第一个质数,第一个指数乘以最后一个质数,最后一个指数,然后与第二个质数相比较下一个)

既然成立,那么它是可减的。

让我们试试[b,b,b,b,b]

这可以编码为

[3^5]

或者,

[3]  // primes
[5]  // exponents

这是一个特殊情况:如果你有1个元素列表,那么你的原始列表是可缩小的。

让我们试试[b, b, b, b, a]

这可以编码为

[3^4, 2^1]

或者,

[3, 2]  // primes
[4, 1]  // exponents

我们检查是否 3^4 == 2^1,由于不是这样,所以您的列表不可简化。

让我们尝试 [a, b, a, b, a, b]

这可以编码为

[2^1, 3^1, 2^1, 3^1, 2^1, 3^1]

或者,

[2, 3, 2, 3, 2, 3]
[1, 1, 1, 1, 1, 1]

尝试上述过程是有效的,因为 2^1 * 3^1 == 3^1 * 2^1 == 2^1 * 3^1

因此,算法应该像这样:


将所有数字编码为质数。

遍历列表,按以下方式创建两个列表并填充它们

现在您拥有两个列表pe,它们的长度均为n,请执行以下操作:

var start = p[0]^e[0] * p[n-1]^e[n-1]
var reducible = true;

for (int i = 0; i < n/2, ++i) :
   if ( (p[i]^e[i] * p[n-i]^e[n-i]) != start ) :
       reducible = false;
       break;

注意:我并没有真正编写这个算法并尝试各种输入。这只是一个想法。 另外,如果一个列表是可约的,从它的长度和n的长度来看,将原始列表缩减到其基本形式应该不太困难。
第二个注意事项:如果有人发现上面有错误,请纠正我。由于时间已经很晚了,我的注意力可能不够集中,所以这些都可能行不通。

0

这里有一些简单的代码,应该可以在接近线性时间内运行(最坏情况下O(n lg lg n),我认为这依赖于一些高等数学)。

f(x) {
  i = 1;
  while (i <= size(x) / 2) {
    if (size(x) % i != 0) { i++; continue;}
    b = true;
    for (j = 0; j + i < x.size(); j++) {
      if (x[i] != x[j]) {
        b = false;
        break;
      }
    }
    if (b) return i;
    i = max(i + 1, j / i * i / 2); // skip some values of i if j is large enough
  }
  return -1;
}

基本上,以上代码执行了朴素算法,但跳过了一些周期性,因为早期的“近似匹配”已经被证明是不可能的。例如,如果您尝试一个周期为5并看到“aaaabaaaabaaaabaaaabab”,则可以安全地跳过6、7、...、10,因为我们看到了4个5的循环重复,然后失败了。

最终,您需要做线性数量的工作加上与sigma(n)成线性的工作量,其中n的约数之和sigma(n)受O(n lg lg n)的限制。

*请注意,证明此跳过的正确性相当微妙,我可能在细节上犯了错误--欢迎评论。


我尝试实现这个,但是在进行最大值评估时,作用域中未定义j。a数组从未被使用。返回值是可以循环生成整个列表的部分字符串的长度?目前无法理解,需要先睡一觉。 - kiteloop
在 for 循环外初始化 j。我原本打算使用数组 a,但找到了一个解决方法(编辑代码)。是的,返回值是重复序列的长度。 - jonderry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接