Princeton Algorithms, Burrows-Wheeler

Burrows–Wheeler

Princeton Algorithm Assignment Burrows–Wheeler

普林斯顿大学算法课 Burrows–Wheeler

Burrows–Wheeler 算法是一个革命性的压缩算法,可以对 gzipPKZIP 进行压缩,并且构成了 Unix 系统压缩工具 bzip2 的基础,该算法分为 3 个主要的部分:

  1. Burrows–Wheeler 变换。给定一段英文文本,将其转化为具有如下格式的文本序列:相同的字符会在相邻的位置出现多次。
  2. Move-to-Front 编码。将 Burrows–Wheeler 变换后的文本编码为特定字符出现的频率比其他字符更高的文本。
  3. 对上述编码进行 Huffman 压缩。哈夫曼压缩可将出现频率更高的字符用更短的编码值表示,从而实现高效压缩。

事实上,第 3 步的 Huffman 压缩才是唯一对信息进行压缩的步骤,但是前 2 步的变换和编码可以保证特定字符出现的频率高于其他字符,从而保证 Huffman 压缩具有较高的压缩效率。

为了展开压缩后的信息,我们可以将上述操作逆向进行:首先进行 Huffman 解码,然后进行 Move-to-Front 解码,最后逆向进行 Burrows–Wheeler 变换。

Princeton 的 Algorithm 课程作业要求实现 Burrows–Wheeler 变换和 Move-to-Front 编码,并调用课程所学 Huffman 算法完成完整的压缩程序。为了方便代码调试,课程提供了 BinaryStdInBinaryStdOut 以及 HexDump 工具。

Move-to-Front 编码和解码的主要思想是通过反复从输入信息中读取一个字符,打印该字符在序列中出现的位置,并将该字符移动到序列的前面,从而保持字母表中字符的有序序列。例如对于 6 个字符的初始序列 A B C D E F,我们对 CAAABCCCACCF 进行加密,我们可以得到:

move-to-front    in   out
-------------    ---  ---
 A B C D E F      C    2 
 C A B D E F      A    1
 A C B D E F      A    0
 A C B D E F      A    0
 A C B D E F      B    2
 B A C D E F      C    2
 C B A D E F      C    0
 C B A D E F      C    0
 C B A D E F      A    2
 A C B D E F      C    1
 C A B D E F      C    0
 C A B D E F      F    5
 F C A B D E  
  • C 进行加密,发现 C 的位置是 2(A 位于 0、B 位于 1、C 位于 2),所以输出结果为 2,接着将 C 移动到序列的最前端,此时序列变为 C A B D E F

  • A 进行加密时,A 出现在序列的位置是 1,所以输出结果为 1,并将 A 移动到序列最前端,此时序列变为 A C B D E F

  • 继续对 A 进行加密,此时 A 出现的位置是 0,所以输出 0,以此类推……

如果在输入中多次出现彼此接近的字符,那么许多输出值将是较小的整数(如 0、1 和 2 等),由此产生的这些字符(较多的 0、1 和 2 等)的频率会很高,提供了 Huffman 编码所能达到的、有利压缩比的输入。例如 CAAABCCCACCF 的编码中出现了 5 次 0、2 次 1 和 4 次 2。

Move-to-Front 编码的任务是依次读入每一个字节(8 个二进制位,看作字符 char),输出其在序列中的位置,并将其移动到最前面。Move-to-Front 解码的任务是依次读入每一个字节(8 个二进制位,看作 0 - 255 之间的无符号整数),输出这个整数所代表的位置上的字符,并将改字符移动到序列最前面。

这一部分的代码比较简单,只需要使用 BinaryIn 和 BinaryOut 即可,注意运行到最后要 flush() 刷新输入输出缓冲区,并注意输入输出整型时,需要指定只输出或读入 8 位。

LinkedList<Character> sequence = generateInitialSequence();
while (!in.isEmpty()) {
   
    char c = in.readChar();
    int index = sequence.indexOf(c);
    out.write(index, RELEVANT_BITS);
    sequence.remove((Object)c);
    sequence.addFirst(c);
}
out.flush(); // out.close();

由于没有 remove(char c) 的函数签名,所以 char c 会被当做 int index,并执行 remove(int index),在 LinkedList 中,这是代表移除第 index 个位置上的元素。我们希望的是移除那个内容为 c 的元素,所以这里强制转换成了 Object,这样就会调用 remove(Object o) 去移除那个内容为 o 的元素。

为了高效地进行 Burrows–Wheeler 变换,我们需要环形后缀数组。

例如对于一个长度为 12 的字符串 ABRACADABRA!,我们通过每次将其循环移动 1 位,可以得到 12 个不同的字符串(记为 Original Suffixes)。然后将这 12 个字符串按照字典序排序(记为 Sorted Suffixes)。

我们定义 index[i] 为排序后数组(Sorted Suffixes)中出现第 i 个原后缀(Original Suffixes)的索引。例如,index[11] = 2 意味着第 2 个原后缀(R A C A D A B R A ! A B)出现在排序顺序中的第 11 位。

 i       Original Suffixes           Sorted Suffixes         index[i]
--    -----------------------     -----------------------    --------
 0    A B R A C A D A B R A !     ! A B R A C A D A B R A    11
 1    B R A C A D A B R A ! A     A ! A B R A C A D A B R    10
 2    R A C A D A B R A ! A B     A B R A ! A B R A C A D    7
 3    A C A D A B R A ! A B R     A B R A C A D A B R A !    0
 4    C A D A B R A ! A B R A     A C A D A B R A ! A B R    3
 5    A D A B R A ! A B R A C     A D A B R A ! A B R A C    5
 6    D A B R A ! A B R A C A     B R A ! A B R A C A D A    8
 7    A B R A ! A B R A C A D     B R A C A D A B R A ! A    1
 8    B R A ! A B R A C A D A     C A D A B R A ! A B R A    4
 9    R A ! A B R A C A D A B     D A B R A ! A B R A C A    6
10    A ! A B R A C A D A B R     R A ! A B R A C A D A B    9
11    ! A B R A C A D A B R A     R A C A D A B R A ! A B    2

CircularSuffixArray 的任务是完成一个函数,方便获得 index[i]

注意由于空间复杂度要求是 n + R n+R n+R,所以我们不能求出整个 Sorted Suffixes 数组,因为长度为 n n n 的字符串一共可以有 n n n 个不同的循环表示,最终会得到一个 n 2 n^2 n2 的大小,但是又因为调用 index() 是需要在常数时间内完成的,所以我们必须在构造时就完成整个 index[] 数组的计算。

给定一个原后缀,它应该排在 Sorted Suffixes 数组中的第几位呢?

如果我们发现它的首字母是所有 n n n 个字符中第 k k k 大的,那它必定是在 Sorted Suffixes 数组中的第 k k k 或更大的位置上。例如,XABCYABDZ 中的 X 6 6 6 个字符大,所以它必定排在第 6 6 6 位甚至更大的位置上。

那如果是 BDZXABC

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凝神长老

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值