【问题标题】:How to efficiently store a matrix with highly-redundant values如何有效地存储具有高度冗余值的矩阵
【发布时间】:2011-03-07 03:33:54
【问题描述】:

我有一个非常大的矩阵(100M 行 x 100M 列),其中有很多相邻的重复值。例如:

8 8 8 8 8 8 8 8 8 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 8 8 8 8 8 8 8 8 8 8 8 8
8 8 3 3 3 3 3 3 3 3 3 3 3

我想要一种数据结构/算法来尽可能紧凑地存储此类矩阵。例如,上面的矩阵应该只占用 O(1) 的空间(即使矩阵被拉伸到任意大),因为只有恒定数量的矩形区域,每个区域只有一个值。

重复发生在行和下列中,因此逐行压缩矩阵的简单方法还不够好。 (这将需要最少 O(num_rows) 空间来存储任何矩阵。)

矩阵的表示也需要逐行访问,这样我就可以对列向量进行矩阵乘法。

【问题讨论】:

  • 这是什么应用程序?我以前从未见过这种结构与矩阵乘法一起使用的矩阵。
  • 该矩阵是一个非常大的 2 人零和游戏的收益矩阵。乘法是针对代表一个玩家的混合策略的向量(每个元素是使用该策略的概率)。
  • 您上面的示例并没有清楚地表明矩阵是稀疏的,但我在您的评论中注意到(99% 为空)。我提到的算法和结构将有效地存储它。您可能还想利用矩阵 A 中包含相同值 (x) 的行简化 AB 到 Sum(B) * x 的计算这一事实。您可以使用它来显着减少需要进行的计算次数(您可以存储 Sum(B))。

标签: algorithm data-structures sparse-matrix matrix-multiplication


【解决方案1】:

您可以将矩阵存储为quadtree,其叶子包含单个值。将其视为价值的二维“运行”。

【讨论】:

  • 节点是否会根据数据的形状进行调整,还是每个细分总是分成 4 等份?
  • 如果您将叶子注释定义为单个值,那么您必须进行划分以适应数据。但两者都有可能。
  • 您递归地将任何方形数组细分为 4 个子数组,当(子)数组在任何地方都包含相同的值时停止。一个四边形细分可能有两个具有不同常数且不会进一步细分的子单元,以及两个进一步划分的子单元。这是可行的,因为在最坏的情况下,您可以将数组细分为单个单元格,它们显然会形成一个仅包含单个值的 1x1 矩阵!通过递归遍历四叉树来访问任何数组元素是 O(log(n)),如果矩阵是稀疏的,平均访问时间将大大少于最坏情况。
【解决方案2】:

现在是我的首选方法。

好的,正如我在之前的答案中提到的,矩阵 A 中每一列中具有相同条目的行将乘以矩阵 AB 中的相同结果。如果我们能保持这种关系,那么理论上我们可以显着加快计算速度(分析器是你的朋友)。

在这个方法中我们维护了矩阵的行*列结构。

每一行都使用任何可以足够快地解压缩而不会过多影响乘法速度的方法进行压缩。 RLE 可能就足够了。

我们现在有一个压缩行列表。

我们使用熵编码方法(如 Shannon-Fano、Huffman 或算术编码),但我们不使用此压缩行中的数据,我们使用它来压缩行集。 我们用它来编码行的相对频率。 IE。我们对待行的方式与标准熵编码对待字符/字节的方式相同。

在此示例中,RLE 压缩 a 行,而 Huffman 压缩整个 行集

因此,例如,给定以下矩阵(以行号为前缀,Huffman 用于便于解释)

0 | 8 8 8 8 8 8 8 8 8 8 8 8 8 |
1 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
2 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
3 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
4 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
5 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
6 | 8 8 8 8 8 8 8 8 8 8 8 8 8 |
7 | 8 8 3 3 3 3 3 3 3 3 3 3 3 |

行程编码

0 | 8{13}                    |
1 | 8{1} 4{1} 8{2} 1{5} 8{4} |
2 | 8{1} 4{1} 8{2} 1{5} 8{4} |
3 | 8{1} 4{1} 8{2} 1{5} 8{4} |
4 | 8{1} 4{1} 8{2} 1{5} 8{4} |
5 | 8{1} 4{1} 8{2} 1{5} 8{4} |
6 | 8{13}                    |
7 | 8{2} 3{11}               |

因此,0 和 6 出现两次,1 – 5 出现 5 次。 7 只有一次。

频率表

A: 5 (1-5) | 8{1} 4{1} 8{2} 1{5} 8{4} |
B: 2 (0,6) | 8{13}                    |
C: 1    7  | 8{2} 3{11}               |

霍夫曼树

    0|1
   /   \
  A    0|1
      /   \
     B     C

因此,在这种情况下,对第 1 到第 5 行进行编码需要一位(对于每一行),对第 0、6 和 7 行进行编码需要 2 位。

(如果运行的长度超过几个字节,则对您在执行 RLE 时建立的散列进行频率计数)。

您存储 Huffman 树、唯一字符串和行编码比特流。

霍夫曼的好处是它有一个独特的前缀属性,所以你总是知道什么时候完成。因此,给定位串10000001011,您可以从存储的唯一字符串和树中重建矩阵 A。编码的比特流告诉你行出现的顺序。

您可能想研究自适应霍夫曼编码,或其算术对应物。

看到 A 中具有相同列条目的行在向量 B 上乘以 AB 中的相同结果,您可以缓存结果并使用它而不是再次计算它(如果可以的话,避免 100M*100M 乘法总是好的) .

更多信息的链接:

Arithmetic Coding + Statistical Modeling = Data Compression

Priority Queues and the STL

Arithmetic coding

Huffman coding

比较

未压缩

    0   1   2   3   4   5   6   7
  =================================
0 | 3   3   3   3   3   3   3   3 |
  |-------+               +-------|
1 | 4   4 | 3   3   3   3 | 4   4 |
  |       +-----------+---+       |
2 | 4   4 | 5   5   5 | 1 | 4   4 |
  |       |           |   |       |
3 | 4   4 | 5   5   5 | 1 | 4   4 |
  |---+---|           |   |       |
4 | 5 | 0 | 5   5   5 | 1 | 4   4 |
  |   |   +---+-------+---+-------|
5 | 5 | 0   0 | 2   2   2   2   2 |
  |   |       |                   |
6 | 5 | 0   0 | 2   2   2   2   2 |
  |   |       +-------------------|
7 | 5 | 0   0   0   0   0   0   0 |
  =================================

= 64 字节

四叉树

    0   1   2   3   4   5   6   7
  =================================
0 | 3 | 3 |       |       | 3 | 3 |
  |---+---|   3   |   3   |---+---|
1 | 4 | 4 |       |       | 4 | 4 |
  |-------+-------|-------+-------|
2 |       |       | 5 | 1 |       |
  |   4   |   5   |---+---|   4   |
3 |       |       | 5 | 1 |       |
  |---------------+---------------|
4 | 5 | 0 | 5 | 5 | 5 | 1 | 4 | 4 |
  |---+---|---+---|---+---|---+---|
5 | 5 | 0 | 0 | 2 | 2 | 2 | 2 | 2 |
  |-------+-------|-------+-------|
6 | 5 | 0 | 0 | 2 | 2 | 2 | 2 | 2 |
  |---+---+---+---|---+---+---+---|
7 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
  =================================

0 +- 0 +- 0 -> 3
  |    +- 1 -> 3
  |    +- 2 -> 4
  |    +- 3 -> 4
  +- 1      -> 3
  +- 2      -> 4
  +- 3      -> 5
1 +- 0      -> 3
  +- 1 +- 0 -> 3
  |    +- 1 -> 3
  |    +- 2 -> 4
  |    +- 3 -> 4
  +- 2 +- 0 -> 5
  |    +- 1 -> 1
  |    +- 2 -> 5
  |    +- 3 -> 1
  +- 3      -> 4
2 +- 0 +- 0 -> 5
  |    +- 1 -> 0
  |    +- 2 -> 5
  |    +- 3 -> 0
  +- 1 +- 0 -> 5
  |    +- 1 -> 5
  |    +- 2 -> 0
  |    +- 3 -> 2
  +- 2 +- 0 -> 5
  |    +- 1 -> 0
  |    +- 2 -> 5
  |    +- 3 -> 0
  +- 3 +- 0 -> 0
       +- 1 -> 2
       +- 2 -> 0
       +- 3 -> 0
3 +- 0 +- 0 -> 5
  |    +- 1 -> 1
  |    +- 2 -> 2
  |    +- 3 -> 2
  +- 1 +- 0 -> 4
  |    +- 1 -> 4
  |    +- 2 -> 2
  |    +- 3 -> 2
  +- 2 +- 0 -> 2
  |    +- 1 -> 2
  |    +- 2 -> 0
  |    +- 3 -> 0
  +- 3 +- 0 -> 2
       +- 1 -> 2
       +- 2 -> 0
       +- 3 -> 0

((1*4) + 3) + ((2*4) + 2) + (4 * 8) = 49 leaf nodes 
49 * (2 + 1) = 147 (2 * 8 bit indexer, 1 byte data)
+ 14 inner nodes -> 2 * 14 bytes (2 * 8 bit indexers)
= 175 Bytes

区域哈希

    0   1   2   3   4   5   6   7
  =================================
0 | 3   3   3   3   3   3   3   3 |
  |-------+---------------+-------|
1 | 4   4 | 3   3   3   3 | 4   4 |
  |       +-----------+---+       |
2 | 4   4 | 5   5   5 | 1 | 4   4 |
  |       |           |   |       |
3 | 4   4 | 5   5   5 | 1 | 4   4 |
  |---+---|           |   |       |
4 | 5 | 0 | 5   5   5 | 1 | 4   4 |
  |   + - +---+-------+---+-------|
5 | 5 | 0   0 | 2   2   2   2   2 |
  |   |       |                   |
6 | 5 | 0   0 | 2   2   2   2   2 |
  |   +-------+-------------------|
7 | 5 | 0   0   0   0   0   0   0 |
  =================================

0: (4,1; 4,1), (5,1; 6,2), (7,1; 7,7)         | 3
1: (2,5; 4,5)                                 | 1
2: (5,3; 6,7)                                 | 1
3: (0,0; 0,7), (1,2; 1,5)                     | 2
4: (1,0; 3,1), (1,6; 4,7)                     | 2
5: (2,2; 4,4), (4,0; 7,0)                     | 2

区域:(3 + 1 + 1 + 2 + 2 + 2) * 5 = 55 字节{4 字节矩形,1 字节数据)

{查找表是一个排序数组,因此不需要额外的存储空间}。

霍夫曼编码的 RLE

0   | 3 {8}                                 | 1
1   | 4 {2} | 3 {4} | 4 {2}                 | 2
2,3 | 4 {2} | 5 {3} | 1 {1} | 4 {2}         | 4
4   | 5 {1} | 0 {1} | 5 {3} | 1 {1} | 4 {2} | 5
5,6 | 5 {1} | 0 {2} | 2 {5}                 | 3
7   | 5 {1} | 0 {7}                         | 2


RLE Data:    (1 + 3+ 4 + 5 + 3 + 2) * 2 = 36
Bit Stream:   20 bits packed into 3 bytes = 3
Huffman Tree: 10 nodes * 3 = 30
= 69 Bytes

一个巨大的 RLE 流

3{8};4{2};3{4};4{4};5{3};1{1};4{4};5{3};1{1};4{2};5{1};0{1};
5{3};1{1};4{2};5{1};0{2};2{5};5{1};0{2};2{5};5{1};0{7}

= 2 * 23 = 46 Bytes

一个使用公共前缀折叠编码的巨型 RLE 流

3{8};
4{2};3{4};
4{4};5{3};1{1};
4{4};5{3};
1{1};4{2};5{1};0{1};5{3};
1{1};4{2};5{1};0{2};2{5};
5{1};0{2};2{5};
5{1};0{7}

0 + 0 -> 3{8};4{2};3{4};
  + 1 -> 4{4};5{3};1{1};

1 + 0 -> 4{2};5{1} + 0 -> 0{1};5{3};1{1};
  |                + 1 -> 0{2}
  |
  + 1 -> 2{5};5{1} + 0 -> 0{2};
                   + 1 -> 0{7}

3{8};4{2};3{4}           | 00
4{4};5{3};1{1}           | 01
4{4};5{3};1{1}           | 01
4{2};5{1};0{1};5{3};1{1} | 100
4{2};5{1};0{2}           | 101
2{5};5{1};0{2}           | 110
2{5};5{1};0{7}           | 111

Bit stream: 000101100101110111
RLE Data:  16 * 2 = 32
Tree:   : 5 * 2 = 10 
Bit stream: 18 bits in 3 bytes = 3
= 45 bytes

【讨论】:

  • 只是为了兴趣,如果我们把原始矩阵作为104字节,每个条目1字节,那么压缩后的版本总共是19字节。
【解决方案3】:

如果您的数据是真正常规的,您可能会受益于以结构化格式存储它;例如您的示例矩阵可能存储为以下“填充矩形”指令列表:

(0,0)-(13,7) = 8
(4,1)-(8,5)  = 1

(然后要查找特定单元格的值,您将向后遍历列表,直到找到包含该单元格的矩形)

【讨论】:

  • 不幸的是不是那么正常。平均而言,可能有 10 到 1000 个相邻的重复值。但除了所有冗余之外,矩阵也非常稀疏(99.99% 的单元格为零)。
  • @Dustin:那么,您可以使用submatrices 来包含(大部分)非零的区域;您甚至可以在一种树结构中使用这些子矩阵的子矩阵。 (这样的树有术语吗?)它可能不如四叉树高效,但我想你可以找到一些有趣的用途。
【解决方案4】:

正如 Ira Baxter 所建议的, 您可以将矩阵存储为四叉树,其中的叶子包含单个值。

最简单的方法是让四叉树的每个节点覆盖一个 2^n x 2^n 的区域, 每个非叶节点指向它的 4 个大小为 2^(n-1) x 2^(n-1) 的子节点。

使用允许不规则细分的自适应四叉树可能会获得更好的压缩效果。 然后每个非叶节点存储切点 (B,G) 并指向其 4 个子节点。 例如,如果某个非叶子节点覆盖了从左上角的 (A,F) 到右下角的 (C,H) 的区域, 然后它的 4 个孩子覆盖区域 (A,F) 至 (B-1, G-1) (A,G) 到 (B-1, H) (B,F) 至 (C,G-1) (B,G) 到 (C,H)。

您将尝试为每个非叶节点选择 (B,G) 切点,使其与数据中的某些实际划分对齐。

例如,假设您有一个矩阵,中间有一个小方块,在其他地方填充了 9 和 0。

使用简单的二次幂四叉树,您最终会得到至少 21 个节点:5 个非叶节点、4 个 9 的叶节点和 12 个零的叶节点。 (如果居中的小方块距离左边缘和上边缘的距离不是精确某个二次方的距离,并且本身不是某个精确的二次方,那么您将获得更多的节点。 /p>

对于自适应四叉树,如果您足够聪明,可以在该正方形的左上角为根节点选择切点,那么对于根的右下子节点,您可以在下方选择一个切点- 正方形的右上角,您可以用 9 个节点表示整个矩阵:2 个非叶节点,1 个叶节点代表 9,6 个叶节点代表 0。

【讨论】:

    【解决方案5】:

    你知道....区间树吗?

    区间树是一种有效存储区间然后查询它们的方法。泛化为Range Tree,可以适应任何维度。

    在这里,您可以有效地描述您的矩形并为其附加值。当然,矩形可以重叠,这将使其高效。

    0,0-n,n --> 8
    4,4-7,7 --> 1
    8,8-8,n --> 3
    

    然后,当查询某个特定点的值时,会返回一个包含几个矩形的列表,需要确定最里面的一个:这就是该点的值。

    【讨论】:

      【解决方案6】:

      最简单的方法是在一个维度上使用游程编码,而不用担心另一个维度。

      (如果数据集不是那么大,将其解释为图像并使用标准的无损图像压缩方法也将非常简单——但由于您必须努力使算法适用于稀疏矩阵,事情最终不会那么简单。)

      另一种简单的方法是尝试矩形泛光填充——从右上角的像素开始,然后将其增加到最大的矩形(宽度优先);然后将所有这些像素标记为“完成”并获取右上角最剩余的像素,重复直到完成。 (您可能希望将这些矩形存储在某种 BSP 或四叉树中。)

      一种高效的技术——不是最优的,但可能已经足够好了——是使用二元空间划分树,其中“空间”不是在空间上而是通过变化的数量来衡量的。您将递归切割,以便在左侧和右侧(或顶部和底部 - 大概您希望保持正方形)具有相同数量的更改,并且随着您的尺寸变小,您可以切割尽可能多的尽可能地改变。最终,您将切割两个彼此分开的矩形,每个矩形都有相同的数字;然后停下来。 (x 和 y 中的 RLE 编码会很快告诉你变化点在哪里。)

      【讨论】:

      • 正如我在问题中提到的那样,行数太多,即使您在压缩每一行时做得很好,它仍然会太大。
      • 对于洪水填充方法,您必须以某种方式逐步进行(因为正在生成矩阵)。根据您的描述,听起来您已经将所有像素放在内存中(我没有,因为它们太多了:)
      • @Dustin - 您可以增量地进行矩形泛洪填充 - 在 x 上进行 RLE 编码,然后如果您有两个长度相同的段,则在 y 中展开。如果您可以存储k RLE 编码的行,则可以使用它们进行本地泛洪填充。 (基本上在 y 中查找相同 x 范围的块,当您在一行中找到 2 个或在 k 中找到 3+ 个时将它们编码为矩形(其他的太长并且可以被切断)。)跨度>
      • @Dustin Boswell:RLE 可能比您想象的更有用。您可以通过将其与我在第二个答案中展示的技术相结合来减少垂直冗余。其次,您可以使用 RLE 和矩阵乘法的属性来显着加快计算速度,即使在不重复的行上也是如此。如果将向量的切片与矩阵行中的运行相加,则只需将运行值乘以一次。在整行上(运行与向量长度相同)您只需要一次乘法,并且您可以缓存向量的总和,因此您只需执行一次。
      • 对于为零的行(所有列中为 0),您可以将结果设置为 0 并移至下一行。此外,在重复行(垂直冗余)上,您可以记住结果,因此您只需计算一次。这不仅可以节省空间,还可以节省处理时间。
      【解决方案7】:

      您对大小为 100M x 100M 的矩阵的 O(1) 空间的描述令人困惑。当你有一个有限矩阵时,你的大小是一个常数(除非生成矩阵的程序没有改变它)。因此,即使将其乘以标量,存储所需的空间量也是一个常数。读取和写入矩阵的时间肯定不会是 O(1)。

      我可以想到稀疏矩阵来减少存储此类矩阵所需的空间量。您可以将此稀疏矩阵写入文件并将其存储为 tar.gz,这将进一步压缩数据。

      我确实有一个问题,100M 中的 M 表示什么?这是否意味着兆字节/百万?如果是,则此矩阵大小将为 100 x 10^6 x 100 x 10^6 字节 = 10^16 / 10^6 MB = 10^10/10^6 TB = 10^4 TB!!!你用的是什么机器?

      【讨论】:

      • M 的意思是“百万”。是的,矩阵很大,天真地存储它不是一种选择。这就是我发布问题的原因:)
      【解决方案8】:

      我不知道为什么这个问题会成为社区 Wiki,但确实如此。

      我将假设您有一个线性代数应用程序,并且您的矩阵具有矩形冗余类型。如果是这样,那么您可以做一些比四叉树更好的事情,并且比将矩阵切割成矩形更干净(这通常是正确的想法)。

      设 M 为矩阵,v 为要乘以 M 的向量,设 A 为特殊矩阵

      A = [1 -1  0  0  0]
          [0  1 -1  0  0]
          [0  0  1 -1  0]
          [0  0  0  1 -1]
          [0  0  0  0  1]
      

      您还需要 A 的逆矩阵,我将其称为 B:

      B = [1 1 1 1 1]
          [0 1 1 1 1]
          [0 0 1 1 1]
          [0 0 0 1 1]
          [0 0 0 0 1]
      

      将向量 v 乘以 A 既快速又简单:您只需获取 v 的连续元素对的差值。将向量 v 乘以 B 也既快速又简单:Bv 的元素是 v 的元素的部分和.那你要使用方程

      Mv = B AMA B v
      

      矩阵 AMA 是稀疏的:在中间,每个条目是 M 的 4 个条目的交替总和,构成 2 x 2 正方形。您必须在 M 中的一个矩形的角处,才能使这个交替和为非零。由于 AMA 是稀疏的,因此您可以将其非零条目存储在关联数组中,并使用稀疏矩阵乘法将其应用于向量。

      【讨论】:

        【解决方案9】:

        对于您显示的矩阵,我没有具体的答案。在有限元分析 (FEA) 中,您拥有包含冗余数据的矩阵。在我的研究生项目中实现 FEA 包时,我使用了天际线存储方法。

        一些链接:

        Intel page for sparse matrix storage

        Wikipedia link

        【讨论】:

          【解决方案10】:

          首先要尝试的始终是现有的库和解决方案。让自定义格式与您最终想要的所有操作一起工作需要做很多工作。稀疏矩阵是一个老问题,所以一定要阅读现有的东西。

          假设您找不到合适的东西,我会推荐一种基于行的格式。不要试图对超级紧凑的表示过于花哨,你最终会得到代码中每个小操作和错误所需的大量处理。而是尝试分别压缩每一行。您知道您将不得不扫描每一行以进行矩阵向量乘法,让自己的生活变得轻松。

          我将从运行长度编码开始,先看看它是如何工作的。一旦它起作用,请尝试添加一些技巧,例如对上一行的部分的引用。因此,一行可能被编码为:126 个零,8 个 1,直接从上面的行复制的 1000 个条目,32 个零。对于您给定的示例,这似乎非常有效。

          【讨论】:

          • 有 100M 行,因此逐行处理数据意味着您至少需要 O(100M * sizeof(compressed_row)) 内存,而我没有。
          【解决方案11】:

          上述许多解决方案都很好。

          如果您正在处理文件,请考虑面向文件 压缩工具,如 compress、bzip、zip、bzip2 和朋友。 它们工作得很好,特别是如果数据包含冗余 ASCII 字符。使用外部压缩工具可消除 代码中的问题和挑战,并将压缩 二进制和 ASCII 数据。

          在您的示例中,您显示的是一个字符编号。 数字 0-9 可以用较小的四位表示 编码模式。您可以使用额外的位 一个字节作为计数。四位给你额外的代码 逃到临时演员... 但是有一个警告到达 回到使用两个字符的旧 Y2K 错误 一年。偏移量的字节编码会给出 255 年,相同的两个字节将跨越所有写入 历史,然后是一些。

          【讨论】:

          • 不幸的是,这个矩阵正在内存中处理。 0-9 数字只是说明的例子。真实数据是完整的整数。
          【解决方案12】:

          您可能想看看GIF format 及其压缩算法。只需将您的矩阵视为位图...

          【讨论】:

            【解决方案13】:

            让我检查一下我的假设,如果只是为了指导我对问题的思考:

            1. 矩阵高度冗余,不一定是稀疏的。
            2. 我们希望最小化存储空间(在磁盘和 RAM 上)。
            3. 我们希望能够将 A[m*n] 与向量 B[n*1] 相乘以得到 AB[m*1] 而无需先解压缩(至少不超过进行计算所需的量)。
            4. 我们不需要随机访问任何 A[i*j] 条目 -- 所有操作都在矩阵上。
            5. 乘法是在线完成的(根据需要),因此必须尽可能高效。
            6. 矩阵是静态的。

            人们可以尝试各种巧妙的方案来检测矩形或自相似性等,但这最终会在进行乘法时损害性能。我提出了2个相对简单的解决方案。

            我将不得不向后工作一点,所以请耐心等待。

            如果数据主要偏向于水平重复,那么以下可能会很好。

            想象一下扁平化为数组的矩阵(这实际上是它存储在内存中的方式)。例如

            A
            | w0 w1 w2 |
            | x0 x1 x2 |
            | y0 y1 y2 |
            | z0 z1 z2 |
            

            变成

            A’
            | w0 w1 w2 x0 x1 x2 y0 y1 y2 z0 z1 z2 |
            

            我们可以使用任何索引[i,j] = i * j.

            因此,当我们进行乘法运算时,我们使用 k = [0..m*n-1] 迭代“矩阵”数组 A',并使用 (k mod n) 索引向量 B 和向量 AB (k 格 n)。 “div”是整数除法。

            例如,A[10] = z110 mod 3 = 110 div 3 = 3 A[3,1] = z1.

            现在,开始压缩。 我们进行正常运行的运行长度编码 (RLE),但针对 A',而不是 A。使用平面阵列,重复序列将更长,因此压缩效果更好。然后在对运行进行编码之后,我们执行另一个过程,我们提取公共子字符串。我们可以进行某种形式的字典压缩,或者将运行数据处理成某种形式的空间优化图,例如基数树/后缀树或您自己创建的合并顶部和尾部的设备。该图应具有数据中所有唯一字符串的表示形式。您可以选择任意数量的方法将流分解为字符串:匹配前缀、长度或其他内容(最适合您的图形),但在运行边界上进行,而不是字节,否则您的解码将变得更加复杂。当我们解压流时,图就变成了状态机。

            我将使用比特流和Patricia trie 作为示例,因为它最简单,但您可以使用其他东西(每个状态的更多比特更改更好的合并等。查找Stefan Nilsson 的论文) .

            为了压缩运行数据,我们针对图表构建了一个哈希表。该表将字符串映射到位序列。您可以通过遍历图并将每个左分支编码为 0 并将右分支编码为 1 来实现(任意选择)。

            处理运行数据并建立一个位串,直到你在哈希表中得到匹配,输出位并清除字符串(位不会在字节边界上,所以你可能需要缓冲直到你得到一个足以写出的序列)。冲洗并重复,直到您处理完完整的运行数据流。您存储图形和比特流。比特流编码的是字符串,而不是字节。

            如果您反转该过程,使用比特流遍历图形,直到您到达叶/终端节点,您会返回原始运行数据,您可以动态解码以生成整数流,然后乘以向量 B 得到 AB。每次运行用完时,您都会读取下一位并查找其相应的字符串。我们不关心我们没有随机访问 A,因为我们只需要在 B 中(B 可以是范围/间隔压缩但不需要)。

            因此,即使 RLE 偏向于水平运行,我们仍然可以获得良好的垂直压缩,因为常见的字符串只存储一次。

            我将在单独的答案中解释另一种方法,因为它已经变得太长了,但是由于矩阵 A 中的重复行乘以 AB 中的相同结果,该方法实际上可以加快计算速度。

            【讨论】:

              【解决方案14】:

              好的,你需要一个压缩算法试试 RLE(运行长度编码)当数据是 高度冗余。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-09-08
                • 2016-06-07
                • 2015-02-07
                • 2016-02-15
                • 2013-07-16
                • 1970-01-01
                相关资源
                最近更新 更多