既然你已经需要实现代码来处理位层,那么这是我的建议:不要存储实际的频率,因为它们在解码时不需要。但你确实需要实际的树。
所以对于每个节点,从根节点开始:
- 如果是叶子节点: 输出1位 + N位字符/字节
- 如果不是叶子节点,输出0位。然后以相同的方式编码两个子节点(先左后右)
读取时,按照以下步骤进行:
- 读取一位比特。如果是1,则读取N位字符/字节,并返回围绕它的新节点,没有子节点。
- 如果比特为0,则对左右子节点进行解码,方法与上述相同,然后返回围绕它们、没有值但带有这些子节点的新节点。
叶子节点基本上是任何没有子节点的节点。
采用这种方法,你可以在编写输出之前计算出精确的输出大小,以确定收益是否足以证明努力的价值。这假设你有一个键值对字典,其中包含每个字符的频率,其中频率是实际发生的数量。
计算的伪代码如下:
Tree-size = 10 * NUMBER_OF_CHARACTERS - 1
Encoded-size = Sum(for each char,freq in table: freq * len(PATH(char)))
计算树的大小需要考虑叶子节点和非叶子节点,而字符数比内联节点少一个。
SIZE_OF_ONE_CHARACTER表示每个字符占用的比特数,这两个值加起来可以得出我的方法对于树和编码数据总共将占用的比特数。
PATH(c)是一个函数/表,会返回从根节点到该字符在树中的位路径。
下面是一个类似于C#的伪代码实现,假定一个字符只是一个简单的字节。
void EncodeNode(Node node, BitWriter writer)
{
if (node.IsLeafNode)
{
writer.WriteBit(1);
writer.WriteByte(node.Value);
}
else
{
writer.WriteBit(0);
EncodeNode(node.LeftChild, writer);
EncodeNode(node.Right, writer);
}
}
读取它的方法如下:
Node ReadNode(BitReader reader)
{
if (reader.ReadBit() == 1)
{
return new Node(reader.ReadByte(), null, null);
}
else
{
Node leftChild = ReadNode(reader);
Node rightChild = ReadNode(reader);
return new Node(0, leftChild, rightChild);
}
}
一个示例(简化,使用属性等)节点实现:
public class Node
{
public Byte Value;
public Node LeftChild;
public Node RightChild;
public Node(Byte value, Node leftChild, Node rightChild)
{
Value = value;
LeftChild = leftChild;
RightChild = rightChild;
}
public Boolean IsLeafNode
{
get
{
return LeftChild == null;
}
}
}
以下是一个具体示例的输出:
输入:AAAAAABCCCCCCDDEEEEE
字符频率:
每个字符只占8位,因此树的大小将为10 * 5 - 1 = 49位。
树可能长这样:
20
----------
| 8
| -------
12 | 3
----- | -----
A C E B D
6 6 5 1 2
因此,每个字符的路径如下(0表示左,1表示右):
- A:00
- B:110
- C:01
- D:111
- E:10
因此,要计算输出大小:
- A:6次出现* 2位=12位
- B:1次出现* 3位=3位
- C:6次出现* 2位=12位
- D:2次出现* 3位=6位
- E:5次出现* 2位=10位
编码字节的总和为12 + 3 + 12 + 6 + 10 = 43位
将其加入树的49位并输出将是92位或12个字节。与原始的20个未编码字符所需的20 * 8个字节相比,您将节省8个字节。
最终输出,包括树在内,如下所示。流中的每个字符(A-E)都编码为8位,而0和1仅为单个位。流中的空格只是用于分隔树和编码数据,并不占据最终输出中的任何空间。
001A1C01E01B1D 0000000000001100101010101011111111010101010
以你在评论中提到的具体示例AABCDEF为例,你将得到以下结果:
输入:AABCDEF
频率:
树形结构:
7
| 4
|
3 2 2
A B C D E F
2 1 1 1 1 1
路径:
- A:00
- B:01
- C:100
- D:101
- E:110
- F:111
树形结构:001A1B001C1D01E1F = 59个比特(位)
数据:000001100101110111 = 18个比特(位)
总共:59 + 18 = 77个比特(位) = 10个字节
由于原始数据是7个8比特(位)字符 = 56个比特(位),因此对于这样小的数据块会有太多的开销。