📜  反转洞穴——惠勒变换

📅  最后修改于: 2022-05-13 01:57:07.548000             🧑  作者: Mango

反转洞穴——惠勒变换

先决条件: Burrows – Wheeler 数据转换算法

为什么是 BWT 的倒数?其背后的主要思想:

1. BWT 算法的非凡之处在于,这种特殊的变换是可逆的,数据开销最小。

2. BWT的逆计算就是撤消BWT,恢复原字符串。可以从这里研究实现该算法的朴素方法。幼稚的方法是速度和内存密集型的,并且需要我们存储 |text|字符串|text| 的循环旋转。

3. 让我们讨论一个更快的算法,我们只有两件事:
一世。 bwt_arr[]排序旋转列表的最后一列,给出为“annb$aa”
ii. 'x'是我们的原始字符串“banana$”出现在排序轮换列表中的行索引。我们可以在下面的示例中看到“x”为 4

Row Index    Original Rotations    Sorted Rotations
 ~~~~~~~~~    ~~~~~~~~~~~~~~~~~~    ~~~~~~~~~~~~~~~~
    0             banana$               $banana
    1             anana$b               a$banan
    2             nana$ba               ana$ban
    3             ana$ban               anana$b
   *4             na$bana               banana$
    5             a$banan               na$bana
    6             $banana               nana$ba

4.一个重要的观察:如果第j个原始旋转(原始旋转向左移动了j个字符)是排序顺序中的第i行,则l_shift[i]按排序顺序记录其中第(j+1)个原始出现旋转。例如,第 0 个原始旋转“banana$”是排序顺序的第 4 行,由于 l_shift[4] 为 3,因此下一个原始旋转“anana$b”是排序顺序的第 3 行。

Row Index  Original Rotations  Sorted Rotations l_shift 
~~~~~~~~~ ~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~  ~~~~~~~
   0           banana$         $banana           4
   1           anana$b         a$banan           0
   2           nana$ba         ana$ban           5
   3           ana$ban         anana$b           6
  *4           na$bana         banana$           3
   5           a$banan         na$bana           1
   6           $banana         nana$ba           2

5. 我们的工作是从我们可用的信息中推断出l_shift[] ,即bwt_arr[]'x' ,并在其帮助下计算 BWT 的倒数。

如何计算 l_shift[] ?
1. 我们知道 BWT 是“annb$aa” 。这意味着我们知道原始字符串的所有字符,即使它们以错误的顺序排列。

2. 通过排序bwt_arr[] ,我们可以重建排序旋转列表的第一列,我们称之为sorted_bwt[]

Row Index    Sorted Rotations   bwt_arr    l_shift  
 ~~~~~~~~~    ~~~~~~~~~~~~~~~~~~~~~~~~~~    ~~~~~~~   
     0         $  ?  ?  ?  ?  ?  a             4
     1         a  ?  ?  ?  ?  ?  n
     2         a  ?  ?  ?  ?  ?  n
     3         a  ?  ?  ?  ?  ?  b
    *4         b  ?  ?  ?  ?  ?  $             3
     5         n  ?  ?  ?  ?  ?  a
     6         n  ?  ?  ?  ?  ?  a

3. 由于'$'在字符串'sorted_bwt[]'中只出现一次,并且旋转是使用循环环绕形成的,我们可以推断出l_shift[0] = 4。类似地, 'b'出现一次,因此我们可以推断出l_shift[4] = 3。

4. 但是,因为'n'出现了两次,所以l_shift[5] = 1 和 l_shift[6] = 2 或者 l_shift[5] = 2 和 l_shift[6] = 1 似乎是模棱两可的。

5. 解决这种歧义的规则是,如果行 i 和 j 都以相同的字母开头并且 i 。这意味着 l_shift[5] = 1 和 l_shift[6] =2。以类似的方式继续, l_shift[]被计算为以下内容。

Row Index    Sorted Rotations   bwt_arr    l_shift  
 ~~~~~~~~~    ~~~~~~~~~~~~~~~~~~~~~~~~~~    ~~~~~~~   
     0         $  ?  ?  ?  ?  ?  a             4
     1         a  ?  ?  ?  ?  ?  n             0
     2         a  ?  ?  ?  ?  ?  n             5
     3         a  ?  ?  ?  ?  ?  b             6
    *4         b  ?  ?  ?  ?  ?  $             3
     5         n  ?  ?  ?  ?  ?  a             1
     6         n  ?  ?  ?  ?  ?  a             2

为什么歧义解决规则有效?

1. 以这样一种方式对旋转进行排序,即第 5 行按字典顺序小于第 6 行。

2. 因此,第 5 行中的五个未知字符必须小于第 6 行中的五个未知字符(因为两者都以'n'开头)。

3. 我们也知道两行之间比以'n'结尾,第 1 行低于第 2 行。

4. 但是,第 5 行和第 6 行中的五个未知字符恰好是第 1 行和第 2 行中的前五个字符,否则这将与旋转已排序的事实相矛盾。

5. 因此, l_shift[5] = 1l_shift[6] = 2。

实施方式:

1.排序 BWT:使用qsort() ,我们将bwt_arr[]的字符按排序顺序排列并存储在sorted_arr[]中。

2.计算l_shift[]:
一世。我们采用一个指针数组struct node *arr[] ,每个指针指向一个链表。

ii.使bwt_arr[]的每个不同字符成为链表的头节点,我们将节点附加到链表,其数据部分包含该字符在bwt_arr[]中出现的索引。

i        *arr[128]           Linked Lists
~~~~~~~~~    ~~~~~~~~~      ~~~~~~~~~~~~~~~~~~~~~~  
   37          $     ----->    4 ->  NULL
   97          a     ----->    0 -> 5 -> 6 -> NULL
   110         n     ----->    1 -> 2 -> NULL
   98          b     ----->    3 -> NULL

iii.制作链表的sorted_bwt[]头部的不同字符,我们遍历链表并获取相应的 l_shift[]值。

int[] l_shift = { 4, 0, 5, 6, 3, 1, 2 };

3. 迭代字符串长度时间,我们用x = l_shift[x]解码 BWT 并输出bwt_arr[x]。

x = l_shift[4] 
     x = 3
     bwt_arr[3] = 'b'

     x = l_shift[3] 
     x = 6
     bwt_arr[6] = 'a'

例子:

Input : annb$aa // Burrows - Wheeler Transform
        4 // Row index at which original message 
          // appears in sorted rotations list 
Output : banana$

Input : ard$rcaaaabb
        3
Output : abracadabra$

以下是上述实现方式的 C 代码:

// C program to find inverse of Burrows
// Wheeler transform
#include 
#include 
#include 
  
// Structure to store info of a node of
// linked list
struct node {
    int data;
    struct node* next;
};
  
// Compares the characters of bwt_arr[]
// and sorts them alphabetically
int cmpfunc(const void* a, const void* b)
{
    const char* ia = (const char*)a;
    const char* ib = (const char*)b;
    return strcmp(ia, ib);
}
  
// Creates the new node
struct node* getNode(int i)
{
    struct node* nn = 
        (struct node*)malloc(sizeof(struct node));
    nn->data = i;
    nn->next = NULL;
    return nn;
}
  
// Does insertion at end in the linked list
void addAtLast(struct node** head, struct node* nn)
{
    if (*head == NULL) {
        *head = nn;
        return;
    }
    struct node* temp = *head;
    while (temp->next != NULL)
        temp = temp->next;
    temp->next = nn;
}
  
// Computes l_shift[]
void* computeLShift(struct node** head, int index,
                    int* l_shift)
{
    l_shift[index] = (*head)->data;
    (*head) = (*head)->next;
}
  
void invert(char bwt_arr[])
{
    int i,len_bwt = strlen(bwt_arr);
    char* sorted_bwt = (char*)malloc(len_bwt * sizeof(char));
    strcpy(sorted_bwt, bwt_arr);
    int* l_shift = (int*)malloc(len_bwt * sizeof(int));
  
    // Index at which original string appears
    // in the sorted rotations list
    int x = 4;
  
    // Sorts the characters of bwt_arr[] alphabetically
    qsort(sorted_bwt, len_bwt, sizeof(char), cmpfunc);
  
    // Array of pointers that act as head nodes
    // to linked lists created to compute l_shift[]
    struct node* arr[128] = { NULL };
  
    // Takes each distinct character of bwt_arr[] as head
    // of a linked list and appends to it the new node
    // whose data part contains index at which
    // character occurs in bwt_arr[]
    for (i = 0; i < len_bwt; i++) {
        struct node* nn = getNode(i);
        addAtLast(&arr[bwt_arr[i]], nn);
    }
  
    // Takes each distinct character of sorted_arr[] as head
    // of a linked list and finds l_shift[]
    for (i = 0; i < len_bwt; i++)
        computeLShift(&arr[sorted_bwt[i]], i, l_shift);
  
    printf("Burrows - Wheeler Transform: %s\n", bwt_arr);
    printf("Inverse of Burrows - Wheeler Transform: ");
    // Decodes the bwt
    for (i = 0; i < len_bwt; i++) {
        x = l_shift[x];
        printf("%c", bwt_arr[x]);
    }
}
  
// Driver program to test functions above
int  main()
{
    char bwt_arr[] = "annb$aa";
    invert(bwt_arr);
    return 0;
}

输出:

Burrows - Wheeler Transform: annb$aa
Inverse of Burrows - Wheeler Transform: banana$

时间复杂度: O(nLogn) 因为 qsort() 需要 O(nLogn) 时间。

练习:在 O(n) 时间内实现 Burrows 的逆 - Wheeler 变换。

资源:
http://www.cs.princeton.edu/courses/archive/fall07/cos226/assignments/burrows.html