求de Bruijn图上与目标串最小编辑距离

求de Bruijn图上与目标串最小编辑距离

题目表述

  • 给出一个 kk 阶 de Bruijn 图,以及字符串 a
  • 求 de Bruijn 图上的一条路径,使其代表的字符串与 aa 的编辑距离尽可能小,并输出编辑过程
  • 其中
    • len(a)≤10000
    • k≤30
    • 字符集 |Σ|≤4 (为ATCG)
    • de Bruijn 图中节点数量 ≤≤ 10000

de Bruijn 图

在图论中,一个 k de Bruijn 图是一个用来表示序列交叠的有向图。
  • 图中的每个节点表示一个长度为 k 的字符串
  • 若两个节点的字符序列有 k−1的交叠即在这两个节点之间连一条有向边
    • 示例 一个 4 阶 de Bruijn*图
      ABCD -> BCDE -> CDEF
  • 因此在一个 k 阶 de Bruijn 图上,一个长度为 l 的路径可表示一个长度为 k+l−1 的字符串

思路

大概思路就是在图上跑动态规划
编辑距离
  • 定义函数edit(i,j)表示第一个字符串(记为a)前i个字符的子串与第二个字符串(记为b)前j个字符的子串的编辑距离
  • 使edit(i, j) == min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) },当第一个字符串的第i个字符不等于第二个字符串的第j个字符时,f(i, j) = 1;否则,f(i, j) = 0。
  • 其中 edit(i-1, j) + 1代表最后一步为a串删除最后一个字符
  • edit(i, j-1) + 1 代表最后一步为a串增加字符
  • edit(i-1, j-1)+ f(i, j)代表替换字符或者不操作
正确性证明

首先串a最小编辑成串b,那么:
1. 插入的字符不能被替换或删除.
2. 替换的字符不能被替换或删除.
否则的话显然不是最小编辑。也就是说最小编辑只能对字符串a已经存在的字符,至多操作一次。要么删除要么替换,要么不操作。现在我们考虑串a转换成串b,考虑b[j]的来源。分为三种情况:
1. 串a的某个字符
2. 串a的某个字符替换得到的
3. 串a增加一个字符得到的
对于情况1,考虑串a的最后字符(记为a[i])的情况,分为以下几种情况:
1.1 a[i]被删除,那么情况就转化到dp(i-1,j),
dp(i,j)=dp(i-1,j)+1
1.2 a[i]被替换,不可能。因为不能满足b[j]是串a的字符。
1.3 a[i]==b[j], 情况转化成dp(i-1,j-1)
对于情况2,考虑串a的最后字符(记为a[i])的情况,分为以下几种情况:
2.1 a[i]被删除,那么情况就转化到dp(i-1,j)
2.2 a[i]被替换b[j], 情况转化成dp(i-1,j-1)
2.3 a[i]被替换但不是b[j], 不可能。因为不能满足b[j]是串a的字符替换这个条件。
2.4 a[i]保留不动,不可能。因为不能满足b[j]是串a的字符替换这个条件。
对于情况3,情况直接转化到dp(i,j-1).
综上,递归求解过程正确。

建图
  • 图上的每个节点代表一个k (k<30)长度的字符串, 这边使用long long 类型储存, 也就是说将字符串转成4进制的整数
  • 使用的hash函数直接用C++的unordered_map库,将long long类型数据映射到0 ~ n - 1
  • 若节点i 有到节点j 的边,代表节点i的后k-1字符与节点j的前k-1字符相同,如节点i(ATCG),节点j(TCGG)
主要过程
以图上的每一个节点为起始节点,使用BFS,获得新的串的编辑距离,其中fdp(i, j)代表从节点start开始到节点i组成的字符串与目标字符串的前j个字符的编辑距离。如start=0, 节点0(AATC), 节点1(ATCC), 目标串(AAABB), 则fdp(1, 3)代表(AATCC)与(AAAB)编辑距离
需要注意
  • 从节点start到节点i表述不够准确,因为图中可能有环,所以节点i可能出现多次,但是每次表示的字符串都不一样,所以应该说是上次串长度加1组成的新串。同时,为了防止BFS死循环(因为出现环),我们在BFS时必须记录当前节点是否以前出现过,如果出现过,要考虑这次的编辑距离是否更新过,即fdp(i, j)是否为目前出现过最小的, 否则不加入队列
  • 同时,由于数据范围较大,所以动态规划使用循环数组,也就是fdp(i)为循环数组
  • 维护队列q,进行BFS, q中储存的是需要更新的新串的编辑距离
  • 维护helper队列,储存fdp数组编号信息,如对于节点i,先在helper队列获取编号p, 则fdp[p]代表fdp(i)了,同时满足约束条件helper.front一定是helper.end的前驱。
  • 打印转换序列就是再求一次编辑距离,获取状态,依次打出即可
    int findShortID(int start){
        //clearToZero();
        vector<vector<int>> dp(k + 1, vector<int>(dstlen + 1));
        long long a = strMap[start];
        string &b = dstStr;
        for (int i = 0; i <= k; i++){
            dp[i][0] = i;
        }
        for (int j = 0; j <= dstlen; j++){
            dp[0][j] = j;
        }
        for (int i = 0; i < k; i++){
            int c1 = getCharAt(a, i);
            for (int j = 0; j < dstlen; j++){
                char c2 = b[j];
                if (c1 == c2){
                    dp[i + 1][j + 1] = dp[i][j];
                }
                else{
                    int replace = dp[i][j] + 1;
                    int delet = dp[i][j + 1] + 1;
                    int insert = dp[i + 1][j] + 1;
                    int min = replace;
                    if (min > insert){
                        min = insert;
                    }
                    if (min > delet){
                        min = delet;
                    }
                    dp[i + 1][j + 1] = min;
                }
            }
        }
        // 
        tempAns.bestdis = 0.31 * dstlen;
        //tempAns.str = strMap[start];
        queue<int> que;
        Node* p = nodeMap[start];
        int cnum = 0;
        while (p)
        {
            que.push(p->v);
            p = p->next;
            cnum++;
        }
        Trie strTree;
        TrieNode* curChar = strTree.root;
        for (int i = 0; i < k; i++){
            TrieNode* temp = strTree.insert(curChar, getCharAt(a, i));
            curChar = temp;
        }
        FDPHelper helper;
        fdp[helper.push(cnum)].assign(dp[k].begin(), dp[k].end());
        fdpstrings[0] = curChar;
        tempAns.str = strTree.getString(fdpstrings[0]);
        vector<int> checkTimeMap(totalNode + 1);
        checkTimeMap[start] = 1;
        while (!que.empty()){
            int strindex = que.front();
            que.pop();
            long long str = strMap[strindex];
            char c1 = getCharAt(str, k - 1);
            Node* p = nodeMap[strindex];
            int cnum = 0;
            while (p)
            {
                p = p->next;
                cnum++;
            }
            int dpcurindex = helper.push(cnum);
            int dppreindex = helper.addone();
            fdp[dpcurindex][0] = fdp[dppreindex][0] + 1;
            for (int j = 0; j < dstlen; j++){
                char c2 = b[j];
                if (c1 == c2){
                    fdp[dpcurindex][j + 1] = fdp[dppreindex][j];
                }
                else{
                    int replace = fdp[dppreindex][j] + 1;
                    int delet = fdp[dppreindex][j + 1] + 1;
                    int insert = fdp[dpcurindex][j] + 1;
                    int min = replace;
                    if (min > insert){
                        min = insert;
                    }
                    if (min > delet){
                        min = delet;
                    }
                    fdp[dpcurindex][j + 1] = min;
                }
            }
            bool flag = false;
            if (fdp[dpcurindex][dstlen] < tempAns.bestdis){
                tempAns.bestdis = fdp[dpcurindex][dstlen];
                cout << tempAns.bestdis << endl;
                fdpstrings[dpcurindex] = strTree.insert(fdpstrings[dppreindex], c1);
                tempAns.str = strTree.getString(fdpstrings[dpcurindex]);
                return tempAns.bestdis;
                flag = true;
            }
            if (checkTimeMap[strindex] == 0 || tempAns.bestdis == fdp[dpcurindex][dstlen]){
                p = nodeMap[strindex];
                while (p)
                {
                    que.push(p->v);
                    p = p->next;
                }
                if (!flag) fdpstrings[dpcurindex] = strTree.insert(fdpstrings[dppreindex], c1);
            }
            else if (tempAns.bestdis != fdp[dpcurindex][dstlen]){
                helper.reverse();
            }
            checkTimeMap[strindex]++;
        }
        strTree.clearTree(strTree.root);
        return tempAns.bestdis;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值