反转洞穴——惠勒变换
先决条件: 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
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] = 1和l_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