Algorithm 在10亿个序列的列表中查找前N个最频繁的数字序列
假设我有以下列表:Algorithm 在10亿个序列的列表中查找前N个最频繁的数字序列,algorithm,Algorithm,假设我有以下列表: x = [[1, 2, 3, 4, 5, 6, 7], # sequence 1 [6, 5, 10, 11], # sequence 2 [9, 8, 2, 3, 4, 5], # sequence 3 [12, 12, 6, 5], # sequence 4 [5, 8, 3, 4, 2], # sequence 5 [1, 5], # sequence 6 [2, 8, 8, 3, 5, 9, 1,
x = [[1, 2, 3, 4, 5, 6, 7], # sequence 1
[6, 5, 10, 11], # sequence 2
[9, 8, 2, 3, 4, 5], # sequence 3
[12, 12, 6, 5], # sequence 4
[5, 8, 3, 4, 2], # sequence 5
[1, 5], # sequence 6
[2, 8, 8, 3, 5, 9, 1, 4, 12, 5, 6], # sequence 7
[7, 1, 7, 3, 4, 1, 2], # sequence 8
[9, 4, 12, 12, 6, 5, 1], # sequence 9
]
本质上,对于任何包含目标编号5
(即target=5
)的列表,长度M=4
的最常见子序列是什么
因此,条件是:
target
,则我们完全忽略该列表M
,则完全忽略该列表M
,但target
不在Mth
位置,则我们忽略它(但如果target
在Mth
位置,则我们计算它)L
大于M
且target
位于i=M
位置(或i=M+1位置,或i=M+2位置,…
i=L位置),则我们计算长度的子序列M,其中target`位于子序列的最终位置
subseqs = [[2, 3, 4, 5], # taken from sequence 1
[2, 3, 4, 5], # taken from sequence 3
[12, 12, 6, 5], # taken from sequence 4
[8, 8, 3, 5], # taken from sequence 7
[1, 4, 12, 5], # taken from sequence 7
[12, 12, 6, 5], # taken from sequence 9
]
当然,我们想要的是频率最高的N=2
子序列。因此,[2,3,4,5]
和[12,12,6,5]
是计数最多的两个序列。如果N=3
,那么所有的子序列(subseq
)都将返回,因为第三个是平局的
重要
这是超级简化,但实际上,我的实际序列列表
N
和M
可以小到1,也可以大到100N
和M
始终小于100,是否有一种高效的数据结构允许快速查询N
和M
的各种组合,是否有已知的算法来执行此类分析?我看过后缀树,但我必须推出自己的自定义版本,以接近我的需要target
、N
和M
(其中target扩展到我的注释中)。下面是一个如何使用开箱即用后缀数组实现此目的的示意图:
1) 使用停止符号反转并连接列表(我在这里使用0)
2) 建造一个
3) 建立一个新的模型。LCP数组将告诉您一个后缀在后缀数组中与它的邻居有多少个相同的数字。但是,当遇到停止符号时,需要停止计数
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 2, 1, 1, 0, 2, 1, 1, 2, 0, 1, 3, 2, 2, 1, 0, 1, 1, 1, 4, 1, 2, 4, 1, 0, 1, 2, 1, 3, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 2, 1, 2, 0]
4) 当查询传入(target=5,M=4)时,您将在后缀数组中搜索目标的第一次出现,并扫描相应的LCP数组,直到后缀的起始数量发生变化。下面是LCP数组的一部分,它对应于以5开头的所有后缀
[..., 1, 1, 1, 4, 1, 2, 4, 1, 0, ...]
这说明有两个长度为4的序列出现两次。使用索引浏览一些细节,您可以找到序列并将其还原,以获得最终结果
复杂性
- 构建后缀数组是O(n),其中n是所有列表和O(n)空间中元素的总数
- 构建LCP阵列在时间和空间上也是O(n)
- 在后缀中搜索目标编号的平均值为O(logn)
- 扫描相关子序列的成本与目标出现的次数成线性关系。根据给定参数,平均值应为1/10000
前两步在脱机状态下进行。从技术上讲,查询是O(n)(由于步骤4),但有一个小常数(0.0001)。我认为您在错误的StackExchange站点。这是一个现实世界问题的合理数据科学问题。我认为在使用停止符号反转和连接列表后,您可以使用自定义。当你的目标号码出现时,你基本上为所有后缀建立一个以你的目标号码开始的后缀(考虑到停止符号)。如果您希望多个查询具有相同的目标编号和不同的M,那么您也可以预编LCP-Array。如何计算满足条件的两个或多个目标?@Neil这将是“全部”。这正是序列7中发生的事情。然而,它还取决于序列是否进行剪切的N
是什么,因为我们感兴趣的是N
最频繁的子序列,其中target
和lengthM
我正在使用Python。那么,你认为我必须推出自己的自定义后缀数组实现吗?此外,为什么我们需要反转和连接序列?后缀数组可以是标准的,但您可能希望采用LCP数组。由于要查找长度为M且以目标编号结尾的所有子序列,但后缀数组只能帮助您查找以目标编号开头的子序列,因此必须还原序列。你也可以为每个序列建立一个后缀数组,但是串联会更有效。如果我有10到100个长度的10亿个列表,那么空间和时间的复杂性会成为一个问题吗?因为我的输入已经是整数而不是字母,字典排序在这里起到什么作用?现在,通过获取唯一的“状态”并为其分配下一个可用的整数值来生成列表。为了获得更好的性能,我应该关注如何分配这些整数值吗?现在,我在数据中遇到的第一个状态自动分配给整数值1
,第二个状态
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 2, 1, 1, 0, 2, 1, 1, 2, 0, 1, 3, 2, 2, 1, 0, 1, 1, 1, 4, 1, 2, 4, 1, 0, 1, 2, 1, 3, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 2, 1, 2, 0]
[..., 1, 1, 1, 4, 1, 2, 4, 1, 0, ...]