最长公共子序列(nlogn)

来源:互联网 发布:64位windows安装步骤 编辑:程序博客网 时间:2024/05/17 04:38

最长公共子序列(LCS)最常见的算法是时间复杂度为O(n^2)的动态规划(DP)算法,但在James W. Hunt和Thomas G. Szymansky 的论文"A Fast Algorithm for Computing Longest Common Subsequence"中,给出了O(nlogn)下限的一种算法。

 

定理:设序列A长度为n,{A(i)},序列B长度为m,{B(i)},考虑A中所有元素在B中的序号,即A某元素在B的序号为{Pk1,Pk2,..},将这些序号按照降序排列,然后按照A中的顺序得到一个新序列,此新序列的最长严格递增子序列即对应为A、B的最长公共子序列。

 

举例来说,A={a,b,c,d,b},B={b,c,a,b},则a对应在B的序号为2,b对应序号为{4,0},c对应序号为1,d对应为空集,生成的新序列为{2,4, 0, 1, 4, 0},其最长严格递增子序列为{0,1,4},对应的公共子序列为{b, c, b}

 

原论文的证明过程较复杂,其实可以简单的通过一一对应来证明。即证明A、B的一个公共子序列和新序列的一个严格递增子序列一一对应。

(1) A、B的一个公共子序列对应新序列的一个严格递增子序列

假设A、B的某一个公共子序列长度为k,则其公共子序列在A和B中可以写为

{Ai1,Ai2, ..., Aik}

{Bj1,Bj2, ..., Bjk}

 

如此有Ai1 = Aj1,Ai2 = Aj2, ...., Aik = Ajk, 考虑元素Bj1在B中的序号P(Bj1),则有

P(Bj1)< P(Bj2) < ... < P(Bjk)

注意此严格递增子序列属于新序列的一个子序列,因此得证

 

(2) 新序列的一个严格递增子序列对应A、B的一个公共子序列

设新序列的一个严格递增子序列{P1,P2, ..., Pk},任意两个相信的P不可能属于A中同一个元素,因为A中某元素在B中的序号按照降序排列,但此序列为严格递增序列,矛盾。所以每个P均对应于A中不同位置的元素,设为{Ai1, Ai2, ..., Aik}。

因为P是严格递增序列,则每个P也对应B中唯一的一个元素,假设为{Bj1,Bj2, ..., Bjk},由P的定义可知Ai1= Aj1, Ai2 = Aj2, ...., Aik = Ajk,因此得证。

 

实现上比较复杂,有以下几个步骤:

(1) 对序列B排序

(2) 计算A中每个元素在B中的序号,并构成新序列

(3) 使用LIS的方法计算最长严格递增子序列

(4) 获取最长公共子序列


性能分析:

(1) 排序复杂度为nlogn

(2) 获取一个元素在B中的序号的复杂度,最小为logn,最大为n,获取所有元素的复杂度为 nlogn === n*n

(3) LIS 复杂度为nlogn

因此总体复杂度在nlogn 到 n*n logn之间,但如果(2) 步骤中A中元素在B中的序号对数很少时,性能相当优越,在实际测试时,string 中均为小写字母,长度为10000的情况下,这种方法比普通的LCS快一倍以上;如果string 中的字符扩展成char,即0-255,则这种方法比普通的LCS快至少一个数量级。以下为代码实现,可以参考:

/* ================================================================== *//* NlogN for LCS, using LIS *//* ================================================================== */struct lcs_sort {    int val;    int pos;};struct lcs_lis_pos {    int pos_val;    int pos_1;};/* first,  sort string2 */static int qsort_func(const void *s1, const void *s2){    struct lcs_sort *ls1 = (struct lcs_sort *)s1, *ls2 = (struct lcs_sort *)s2;    if (ls1->val > ls2->val)         return 1;    else if (ls1->val < ls2->val)        return -1;    else {        if (ls1->pos > ls2->pos)            return -1;        else            return 1;    }}static void sort_lcs_string(char *string2, int n2, struct lcs_sort **ls_pp){    int i = 0;    struct lcs_sort *ls = NULL;        ls = malloc(sizeof(struct lcs_sort) * n2);    for (i = 0;i < n2;i++) {        ls[i].val = string2[i];        ls[i].pos = i;    }    qsort(ls, n2, sizeof(struct lcs_sort), qsort_func);    *ls_pp = ls;}/* second, find match list for char in string1 and construct a new sequence */static int find_match_list(char ch, struct lcs_sort *ls, int l, int r, int *start){    int m = 0, len, len2;    int start1 = 0, start2 = 0;    if (l > r)        return 0;    else if (l == r) {        if (ch == ls[l].val) {            *start = r;            return 1;        }        return 0;    }    m = (l + r)>>1;    if (ls[m].val < ch)        len = find_match_list(ch, ls, m + 1, r, &start1);    else if (ls[m].val > ch)        len = find_match_list(ch, ls, l, m - 1, &start1);    else {        len = find_match_list(ch, ls, l, m - 1, &start1);        len2 = find_match_list(ch, ls, m + 1, r, &start2) + 1;        if (len == 0)             start1 = m;        len += len2;    }    *start = start1;    return len;}static int match_list_lcs(char *string1, int n1, struct lcs_sort *ls, int n2, struct lcs_lis_pos **lpos_pp){    int i = 0, j = 0, k = 0, len = 0, start = 0;    struct lcs_lis_pos *lpos = NULL;    lpos = malloc(sizeof(struct lcs_lis_pos) * n1 * n2);    for (i = 0;i < n1;i++) {        len = find_match_list(string1[i], ls, 0, n2 - 1, &start);        for (k = 0;k < len;k++) {            lpos[j].pos_val = ls[start + k].pos;            lpos[j++].pos_1 = i;        }    }        *lpos_pp = lpos;    return j;}/* third, find LIS for this new sequence */static inline int find_pos(int *B, int len, int value){    int left = 0, right = len - 1, middle = 0;    while (left <= right) {        /* must finding strictly increasing sub sequence */        middle = (left + right)>>1;        if (B[middle] < value)            left = middle + 1;        else            right = middle - 1;    }        return left;}static int lis_nlogn(struct lcs_lis_pos *p, int len, int *inc_seq){    int i = 0, pos = 0, curr_len = 0;    int *L = NULL, *prev = NULL, *M = NULL;    if (len <= 0)        return 0;    L = malloc(len * sizeof(int));    M = malloc(len * sizeof(int));        prev = malloc(len * sizeof(int));    L[0] = p[0].pos_val;    M[0] = 0;    prev[0] = -1;               /* the prev of the p[0] is NULL */     curr_len = 1;    /* Caculate prev and M */    for (i = 1;i < len;i++) {        pos = find_pos(L, curr_len, p[i].pos_val);        L[pos] = p[i].pos_val;        M[pos] = i;        if (pos > 0)            prev[i] = M[pos - 1];        else            prev[i] = -1;        if (pos + 1 > curr_len)            curr_len++;    }    /* Output increasing sequence */    pos = M[curr_len - 1];    for (i = curr_len - 1;i >= 0 && pos != -1;i--) {        inc_seq[i] = p[pos].pos_1;        pos = prev[pos];    }    return curr_len;}static int calc_lcs_using_lis(char *string1, int n1, char *string2, int n2){    int *inc_seq = NULL;    int list_len = 0, inc_len = 0, len = (n1 < n2) ? n1 : n2;    struct lcs_sort *ls = NULL;    struct lcs_lis_pos *lpos = NULL;    inc_seq = (int *)malloc(sizeof(int) * len);    /* first,  sort string2 */    sort_lcs_string(string2, n2, &ls);    /* second, find match list for char in string1 and construct a new sequence */    list_len = match_list_lcs(string1, n1, ls, n2, &lpos);    /* third, find LIS for this new sequence */    inc_len = lis_nlogn(lpos, list_len, inc_seq);    /* end, get LCS */#if 0    int i = 0;    printf("LCS is: ");    for (i = 0;i < inc_len;i++) {        inc_seq[i] = string1[inc_seq[i]];        printf("%c ", inc_seq[i]);    }    printf("\r\n");#endif    free(ls);    free(lpos);        return inc_len;}


原创粉丝点击