基因比对——查找最长公共子串

查找最长公共子串

题目要求:给定两个基因序列,对两个基因序列进行比对,找到最长的公共子串

设计理念

  1. 二分查找法
  2. Hash字典
  3. 滚动Hash计算hash值;
  4. 线性探查 解决冲突;

二分查找

如果考虑从1开始进行比对,这样时间复杂度将会很大,因此考虑二分法,先从n/2的长度开始比对,如果找到了,就继续查找是否有更长的子串,否则就查找更小的子串,直到到达二分查找的结束条件;

hash字典

选择用字典存储子串,key设置为h1(x1),值是一个列表;采用两个hash函数,能够降低时间复杂度;

滚动hash

计算hash值,每次需要读取一定长度的子串来进行计算,而容易知道,对于相邻的子串,有很大一部分是共有的,因此我们利用这种共性,来简化计算,每次只需要往后面读取一个字符,其余的字符利用上一个子串得到,这样我们就只需要在读取第一个字串时,读取多个字符,其余的子串只需要读取一个字符就可以了,简化了计算;如下图
Alt
这样我们就只需要每次减去前一个子串的第一个字符的权值,再加上最后一个字符的权值,就是新的子串的hash值

线性探查

如果出现了冲突,就采用最简单的线性探查,取i=1,依次往后面进行探查,直到找到一个空的位置,这里的hash表大小刚好为子串个数,因此一定能找到一个位置。
Alt

代码部分

s1 = "SFSDFSDFJLKSJDLFKJALKJSFDJALDJF;AJSDKLFJALKSJDFJA"
s2 = "JWOIJFAFJLAJSDFOIQ;FJALDFJALKDFJSJADFLKJALSDFJLAA"
#s1 = "ABCDEFGHI"
#s2 = "SHABCIJYKL"

n = min(len(s1), len(s2))
base = ord("A") - 1
weight = 26

def create_hash_map(S, L):
    """
    将字符串利用滚动哈希构造一个字典
    这个字典的键是哈希函数 h1 的值,值是一个列表:[x1, h2(x1),num, key],
    num表示这个键的线性探查次数,说明有多少个冲突,key存储的是发生冲突时,原来的键是多少
    :param S: 基因字符串
    :param L: 字串长度
    :return: 哈希字典
    """
    str1 = S[0:L]
    D = {}
    #第一个字符串
    num = ord(str1[0]) - base
    for i in range(1,L):
        num = (num * weight + (ord(str1[i]) - base))
    key = num % (n-L+1)   #h1对n取模
    list1 = [str1, num % ((n-L+1) * (n-L+1)), 0, key]   #h2对n*n取模
    D[key] = list1
    #之后的字符串,滚动哈希求解散列值
    for j in range(L, len(S)):
        str1 += S[j]
        num = weight * (num - (ord(str1[0]) - base) * pow(weight, L - 1)) + ord(str1[L]) - base
        str1 = str1[1:]
        #如果出现冲突,则需要线性探查,找到空的键,添加到字典当中去
        key = num % (n-L+1)
        list1 = [str1, num % ((n-L+1) * (n-L+1)), 0, key]
        if D.get(key) is not None:
            D.get(key)[2] += 1
            for i in range((num % (n-L+1) + 1), 2 * (n-L+1)):
                if D.get(i%(n-L+1)) is None:  #找到了空的键
                    key = i % (n-L+1)
                    break
        D[key] = list1
    return D

def search(key1, key2, str, D, L):
    """
    查找是否有相同的子串
    :param key1: h1(x2)
    :param key2: h2(x2)
    :param str: x2
    :param D: x1的字典
    :return: 存在返回True,不存在返回False
    """
    #第一种情况为第一次直接找到,不需要线性探查
    list1 = D.get(key1)
    if list1 is not None:# 如果存在 h1(x1) == h2(x2)
        if list1[2] == 0:
            if key2 == list1[1]:  #进一步判断是否有 h2(x1) == h2(x2)
                if list1[0] == str:   #子字符串比较
                    return True
        else:  #如果第三个元素不等于零,说明存在冲突项,需要线性探查
            count = list1[2] #线性探查次数
            position = key1
            while count > 0:
                for i in range(position+1, (n-L+1) * 2):
                    list1 = D.get(i % (n-L+1))
                    if list1 is not None:
                        if list1[3] == key1:  #探查到了就计数减一,并及时跳出循环,进行下一次探查
                            if key2 == list1[1]:  #进一步判断是否有 h2(x1) == h2(x2)
                                if list1[0] == str:   #子字符串比较
                                    return True
                            position = i
                            count -= 1
                            break
    return False


def find_max_substring(S, T, left, right):
    L = (left + right) // 2
    if L == left:
        return L
    #将 S 中的子字符串构造成一个哈希表,并且这个表的值是一个列表
    D = create_hash_map(S, L)

    # 第一个字符串的判断
    str1 = T[0:L]
    num = ord(str1[0]) - base
    for i in range(1,L):
        num = (num * weight + (ord(str1[i]) - base))
    key1 = num % (n-L+1)  #h1(x2)
    key2 = num % ((n-L+1)*(n-L+1))  #h2(x2)
    if search(key1, key2, str1, D, L):  #判断是否存在相同的子串
        print(str1)
        return find_max_substring(S, T, L, right)

    #之后的字符串进行判断
    for j in range(L, len(T)):
        str1 += T[j]
        num = weight * (num - (ord(str1[0]) - base) * pow(weight, L - 1)) + ord(str1[L]) - base
        str1 = str1[1:]
        key1 = num % (n-L+1)  #h1(x2)
        key2 = num % ((n-L+1)*(n-L+1))  #h2(x2)
        if search(key1, key2, str1, D, L):  #判断是否存在相同的子串
            print(str1)
            return find_max_substring(S, T, L, right)
    return find_max_substring(S, T, left, L)

#保证构建的hash表要小
if len(s1) <= len(s2):
    print(find_max_substring(s1, s2, 0, n))
else:
    print(find_max_substring(s2, s1, 0, n))

存在问题

在计算hash值的时候,我用的权重是26,当子串很长时,计算的值可能会溢出,这是需要优化的地方
这里提供一种优化思路,
Alt
可以在计算过程中,拆分进行取模运算

后言

如果有问题,欢迎在评论区指出,也欢迎各位大神提出自己的优化实现,拜谢

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值