我有一个大的二进制稀疏矩阵(任何单元格都可以保存0或1作为值)。我不时想拍摄整个矩阵的快照。 快照必须尽可能小。
矩阵表示2d地图和发生在区域中的事件,因此它更像是示例A的快照,而不是看起来像示例B的快照(它们都具有相同数量的" 1& #34;)虽然我需要支持算法中的两个例子。
Example A:
000000000000
000000011000
001100111100
001111111100
000000111100
000001111100
000000000000
Example B:
010010010010
001000001000
010010100100
000100101010
001000010010
010010001100
001000010000
由于数据可能不同于单一" 1"细胞到100%的细胞为" 1" (在非常非常后面的情况下)我认为我需要使用多个算法 - 并且在加载数据时使用与存储它相同的算法加载它。
例如,当只有一个单元格时,我将仅存储它的索引(以及"索引"算法的标识符),当99%的矩阵为" 1&#时34;我将它存储为位图("位图"算法的标识符)。
所以我得到一个像这样的通用算法:
我的问题
底线: 如何以最小的方式存储位图矩阵?
修改 使用案例:我有一个稀疏矩阵,我需要通过一个非常低的带宽介质传输。因此,假设介质两侧的计算能力很强,发送矩阵应包含尽可能少的位。
答案 0 :(得分:5)
数据压缩是一个很大的领域(你可以start here),而且你的问题没有明确的答案。如果我们知道如何以最佳速率压缩数据,那么每年都不会有新的压缩算法;)
话虽这么说,你建议的是一个好主意:从集合中选择最佳算法并在标题中识别它。实际上,大多数压缩格式都使用这种方案。
回答你的问题:
什么算法:
bool
/ boolean
类型都存储在一个完整的字节中)。您必须使用按位运算,专用语言功能,例如bitfields
/ bitsets
或库。例如C++
中,std::vector<bool>
实现了一个真正的位图(简单的bool[]
没有)。0
或仅一个1
是对称的,则应以相同的方式处理。备用存储的最坏情况是拥有与零一样多的存储空间。1111
可能的模式,则可以将min = E( -log2(P(message)) )
存储在少于4位中。但这意味着将使用超过4位存储另一个(不太可能)的4位模式,因此在选择模式时要小心。有很多方法可以做到这一点:固定大小或可变大小的单词,编码可以是静态的,也可以是数据相关的。一些示例包括:RLE,Huffman,LZ变体(您喜欢的压缩程序中使用的基于字典的算法)极简证明:
由于压缩实际上取决于数据的概率分布,因此通常没有最佳压缩算法。实际上,你知道你的矩阵总是一样的,你甚至不需要对它进行编码。你知道它是两个可能的矩阵之一,只有一个就足以编码它是哪一个。
在一般情况下,Shanon的entropy估计编码消息所需的理论最小位数:
E
其中P
是对所有可能消息的期望,P
是概率函数。但是在实践中,你不知道000000000000
000000011000
001100111100
001111111100
000000111100
000001111100
000000000000
,所以你能做到的最好比以前的最佳算法做得更好;)
一般而言,您尝试压缩数据的次数越多,程序就越昂贵,无论是在运行时资源(CPU周期和内存)还是开发工作方面。
示例
只是将它放在例子1 的实践中,给你灵感 - 即使从1个例子设计是一个非常糟糕的主意,因为它几乎没有给你代表性的概率!
初始数据(我将始终省略大小标题,因为它将保持不变) - 位图,84位(25个):
index
在position
方案中,输出一个列表position
。如果您进行概括,则可以使用pattern
+ pattern
列表。例如,0
可以是连续的数量,因此您不需要为一个块输出多个位置。让我们进一步说,我们编码2D模式,定义如下:
1位形状:
1111
表示一行 - 例如1
是一行大小为4的行; 11
11
表示正方形 - 例如:
position
是2的正方形。
然后让我们说取相对而不是绝对位置,这意味着每个-- block #1
00001 position +1
absolute position from top-left, since this is the first block
001 pattern-size 1
0 pattern-shape is row
for size 1, square and row are the same
(which means my coding isn't optimal)
-- block #2
00100 position +4
relative to last position 1, this means position 5
010 pattern-size 2
1 pattern-shape is square
-- decoded output below
0100011000...
0000011000...
0000000000...
.............
编码自前一个位置以来你前进了多少。以您为例,我选择5位位置。以下是解码的工作原理:
10100 (position [0]+20 from top-left)
010 0 (size: 2, shape: row)
00110 (position [20]+6)
010 1 (size: 2, shape: square)
00100 (position +4)
100 1 (size: 4, shape: square)
01000 (position +8)
100 0 (size: 4, shape: row)
11011 (position +27)
001 0 (size 1, shape: row)
因此,使用此方案,您可以使用45位对数据进行编码:
shape
注意:通常,您希望存储标题以了解块的数量(在本例中为5),但您可以从文件/网络有效负载大小中推断出它。同样在这个例子中,我们只需要3个模式大小(1,2,4),因此我们可以将大小存储在两个位上并将其增加到功率2,使有效载荷为40位,但这使得该方案过于专业化。此外,至少有一个无意义的position
(这里有两个:000/0和000/1)是必要的,因为在{{1}中你没有足够的位数编码一个大的&#34;洞&#34;模式之间。例如,这个20 x 3矩阵:
10000000000000000000
00000000000000000000
00000000000000000001
在0和59位置有一个。由于我不能以59位跳转编码59,我们必须跳两次,我们将其编码为:
00000 (+0) 001 0 (a single 1)
11111 (+31) 000 0 (nothing)
11100 (+28) 001 0 (a single 1)
答案 1 :(得分:1)
您已经提到了一些明显的方法来存储这些 - 1
单元格的位图和索引。通过编码“索引”的标识符,可以很容易地扩展索引思想。方法和1
个单元格的数量,后跟那么多对坐标。您甚至可以尝试按行(或列)分组压缩,如
rowNumber colNumber1 colNumber2 ... -1
使用-1
或其他一些标志值来表示行的结尾。这可以为只有少量条目的大尺寸矩阵节省大量空间。您还需要存储矩阵大小。
对于例A,你得到(使用0索引,空白只是为了便于阅读)
7 12 //dimensions
1 7 8 -1
2 2 3 6 7 8 9 -1
3 2 3 4 5 6 7 8 9 -1
4 6 7 8 9 -1
5 5 6 7 8 9 -1
对于您的情况,存储它的方法的另一个示例可能如下 - 您必须进行实验,看看它在压缩&#39;信息。算法的想法来自观察在你的A例子中,几乎所有行都只有一个大的连接部分1s,被0包围。
假设您的矩阵可以有任何维度,第一步就是编码。因此对于n * m矩阵,只需存储这两个整数。
在此之后,对于每一行,以下内容如何:
要解码,您只需遵循以下流程:
count
为目前为止读取的矩阵条目数。初始化为0
next
成为布尔值,表示下一个数字编码的值。初始化为false
。next
的数量的值插入相应的行。next
count
count == m
(如果count > m
出错了)我希望我能很好地解释这个想法。正如我所说,我不知道它的表现如何,但它源于对你的例子的观察。要真正压缩它,您可以确保每个数字条目只需要尽可能多的位来计算(并且不高于)行宽(m
在我的伪代码中。)
示例A会出现类似这样的内容(空白只是为了便于阅读):
7 12 //dimensions
12
7 2 3
2 2 2 4 2
2 8 2
6 4 2
5 5 2
12
如果您对这些数字进行了优化,那么每个数字只需要4位。
我不会尝试做例B,但它的表现会更差