【问题标题】:Repeated DNA sequence重复的 DNA 序列
【发布时间】:2018-12-26 16:59:21
【问题描述】:

问题是找出在给定 DNA 序列中出现不止一次的所有长度为 k 的序列。我找到了一种使用滚动散列函数的方法,其中对于每个长度为 k 的序列,计算散列并将其存储在映射中。为了检查当前序列是否是重复的,我们计算它的散列并检查散列映射中是否已经存在散列。如果是,那么我们将这个序列包含在我们的结果中,否则将其添加到哈希映射中。

这里的滚动哈希是指,当通过滑动窗口移动到下一个序列时,我们使用前一个序列的哈希,即我们移除前一个序列的第一个字符的贡献并添加前一个序列的贡献新添加的 char 即新序列的最后一个字符。

Input: AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT
and k=10
Answer: {AAAAACCCCC, CCCCCAAAAA}

这个算法看起来很完美,但我无法制作一个完美的哈希函数来避免冲突。如果有人可以解释如何在任何情况下以及在这种情况下最重要的是如何制作完美的哈希,那将是一个很大的帮助。

【问题讨论】:

  • 为什么标准的冲突解决方法(链式或探测式)对此不起作用?使用这些方法时,您还可以存储原始键值,以便检查它是否相等

标签: algorithm math hash substring string-hashing


【解决方案1】:

你可以做的是使用中国剩余定理并选择几个大素数模数。如果您还记得,CRT 意味着与互质模量的同余系统具有一个独特的解决方案,它是您所有模量的乘积。因此,如果您有三个模数 10^6+3、10^6+33 和 10^6+37,那么实际上您的模数或多或少为 10^18。有了足够大的模数,您就可以或多或少地忽略发生碰撞的想法——正如我的导师所说的那样,您的计算机更有可能自发着火而不是发生碰撞,因为您可以将碰撞概率尽可能地小。

【讨论】:

    【解决方案2】:

    这实际上是一个研究问题。

    让我们接受一些事实 输入 = N,输入长度 = |N|

    1. 你必须移动一个大小k,这里是k=10,在输入端滑动窗口。因此,您必须使用O(|N|) 或更多。
    2. 您的滚动散列是一种局部敏感的确定性散列,确定性散列的缺点是散列的好处大大减少,因为您遇到类似字符串的次数越多,散列就越难
    3. 输入的时间越长,哈希的效果就越差

    鉴于这些事实,“滚动哈希”很快就会失败。你不能设计一个滚动散列,它甚至适用于染色体的 1/10。

    那么你有什么选择?

    1. Bloom Filters。它们比简单的散列更健壮。缺点是有时他们有误报。但这可以通过使用多个过滤器来缓解。
    2. Cuckoo Hashes 类似于布隆过滤器,但使用更少的内存并具有对局部性敏感的“散列”和最坏情况下的常量查找时间
    3. 只需将每个后缀粘贴在suffix trie 中。完成此操作后,只需输出深度 10 的每个字符串,该字符串还具有至少 2 个孩子,其中一个孩子是叶子。
    4. 使用suffix tree 改进后缀树。查找没有那么简单,但内存消耗更少。
    5. 我最喜欢FM-Index。在我看来,最干净的解决方案使用 Burrows Wheeler 变换。这种技术也用于工业工具,如 Bowtie 和 BWA

    【讨论】:

    • 答案看起来很合适,但除了后缀树之外,我对列出的任何主题都没有足够的知识。
    • “现在每个”?现在每一个什么?
    • @user2357112 这是一个错字,感谢您指出。修好了。
    【解决方案3】:

    提醒:这不是一个通用的解决方案,而是当k 不大时可以使用的一个好技巧。

    诀窍是通过位操作将序列加密为整数。

    如果您的输入 k 相对较小,比如说 10 左右。那么您可以通过位操作在 int 中加密您的 DNA 序列。因为对于序列中的每个字符,只有 4 种可能性,A、C、G、T。您可以简单地制作自己的映射,使用 2 位来表示一个字母。

    例如:00 -> A, 01 -> C, 10 -> G, 11 -> T。

    这样,如果k 为10,则不需要10 个字符的字符串作为哈希键。相反,只能使用整数中的 20 位来表示之前的密钥字符串。

    然后,当您进行滚动哈希时,将存储先前序列的整数左移 2 位,然后使用任何位操作(如 |=)将最后两位设置为新字符。并且记得清除你刚刚移动的最左边的 2 位,这意味着你正在从你的滑动窗口中删除它们。

    通过这样做,一个字符串可以存储在一个整数中,并且就哈希函数计算的复杂性而言,使用该整数作为哈希键可能会更好、更便宜。如果您的输入长度 k 略长于 16,您可以使用 long 值。否则,您可能可以使用位集或位数组。但是将它们散列成为另一个问题。

    因此,当序列长度相对较小时,我会说这个解决方案是解决这个问题的一个很好的尝试,即可以存储在单个整数或长整数中。

    【讨论】:

    • 我认为,这个解决方案会奏效。对于序列 5A5C -> 哈希可以计算为 (10^9)*0+(10^8)*0+(10^7)*0+(10^6)*0+(10^5)*0 +(10^4)*2+(10^3)​​2+(10^2)*2+(10^1)*2+(10^0)*2
    • @JhanakDidwania 我不确定您是否在讨论我的解决方案,因为我的想法是将字符串存储在一个整数中并将该整数用作哈希映射中的哈希键。我认为您不需要在这里提出自己的哈希函数,因为即使您尝试了哈希函数,冲突也基本上是不可避免的。因此,只需在您的编程语言中使用 API。
    【解决方案4】:

    您可以构建suffix array 和LCP array。遍历LCP数组,每次看到大于等于k的值,就报告那个位置所引用的字符串(使用后缀数组判断子字符串来自哪里)。

    由于 LCP 大于或等于 k ​​而报告子字符串后,忽略所有后续值,直到达到小于 k 的值(这样可以避免报告重复值)。

    后缀数组和 LCP 的构建都可以在线性时间内完成。所以总体而言,该解决方案相对于输入加输出的大小是线性的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-19
      • 1970-01-01
      • 1970-01-01
      • 2013-12-20
      • 2020-04-03
      • 2013-09-17
      • 1970-01-01
      • 2018-03-20
      相关资源
      最近更新 更多