编码生成与位运算
本节阅读量:生成Huffman编码
递归前序遍历
Huffman编码的生成通过递归前序遍历实现:
|
|
生成过程图解
假设有如下Huffman树:
root
/ \
A(5) B(7)
/ \ / \
a(2) c(2) b(3) d(4)
执行过程:
调用:generateCodes(root, "")
1. root不是叶子节点
- 调用 generateCodes(A, "0")
2. A不是叶子节点
- 调用 generateCodes(a, "00")
3. a是叶子节点
- huffmanCode['a'] = "00"
- 返回
4. 调用 generateCodes(c, "01")
5. c是叶子节点
- huffmanCode['c'] = "01"
- 返回
6. 调用 generateCodes(B, "1")
7. B不是叶子节点
- 调用 generateCodes(b, "10")
8. b是叶子节点
- huffmanCode['b'] = "10"
- 返回
9. 调用 generateCodes(d, "11")
10. d是叶子节点
- huffmanCode['d'] = "11"
- 返回
最终编码表:
{'a': "00", 'c': "01", 'b': "10", 'd': "11"}
为什么前序遍历?
前序遍历(根→左→右)确保了:
- 每个节点的编码路径是唯一的
- 编码长度与树中节点的深度一致
- 高频字符(更靠近根)获得更短的编码
位运算基础
位运算符介绍:
| 运算符 | 名称 | 示例 | 说明 |
|---|---|---|---|
<< |
左移 | 5 << 2 = 20 |
二进制左移n位,相当于乘以2^n |
>> |
右移 | 20 >> 2 = 5 |
二进制右移n位,相当于除以2^n |
& |
按位与 | 5 & 3 = 1 |
对应位都为1才为1 |
| |
按位或 | 5 | 3 = 7 |
对应位有1就为1 |
^ |
按位异或 | 5 ^ 3 = 6 |
对应位不同才为1 |
~ |
按位取反 | ~5 |
所有位取反 |
左移 <<
|
|
按位与 &
|
|
按位或 |
|
|
按位异或 ^
|
|
位运算 vs 算术运算
| 操作 | 位运算 | 算术运算 |
|---|---|---|
| 乘以2 | x << 1 |
x * 2 |
| 除以2 | x >> 1 |
x / 2 |
| 判断奇偶 | x & 1 |
x % 2 |
位运算的应用
判断整数是否为2的幂
|
|
交换两个变量
|
|
获取某一位的值
|
|
二进制字符串转换为字节
问题场景
假设我们有一个二进制字符串:
"10110011011101" // 14个bit
需要将其转换为字节数组:
[0xB3, 0x74] // [10110011, 01110100]
算法思路
- 从左到右遍历二进制字符串
- 每处理8位,组成一个字节
- 如果不足8位,补0
- 最后需要记录补了多少0(padding)
代码实现
|
|
图解转换过程
输入:"10110011011101"(14位)
处理过程:
初始状态:
byte = 00000000, bitCount = 0
第1位 '1':
byte = 00000000 << 1 = 00000000
byte = 00000000 | 1 = 00000001
bitCount = 1
第2位 '0':
byte = 00000001 << 1 = 00000010
bitCount = 2
第3位 '1':
byte = 00000010 << 1 = 00000100
byte = 00000100 | 1 = 00000101
bitCount = 3
第4位 '1':
byte = 00000101 << 1 = 00001010
byte = 00001010 | 1 = 00001011
bitCount = 4
第5位 '0':
byte = 00001011 << 1 = 00010110
bitCount = 5
第6位 '0':
byte = 00010110 << 1 = 00101100
bitCount = 6
第7位 '1':
byte = 00101100 << 1 = 01011000
byte = 01011000 | 1 = 01011001
bitCount = 7
第8位 '1':
byte = 01011001 << 1 = 10110010
byte = 10110010 | 1 = 10110011
bitCount = 8
→ 保存字节 0xB3,重置 byte=0, bitCount=0
第9位 '0':
byte = 00000000 << 1 = 00000000
bitCount = 1
第10位 '1':
byte = 00000000 << 1 = 00000000
byte = 00000000 | 1 = 00000001
bitCount = 2
第11位 '1':
byte = 00000001 << 1 = 00000010
byte = 00000010 | 1 = 00000011
bitCount = 3
第12位 '1':
byte = 00000011 << 1 = 00000110
byte = 00000110 | 1 = 00000111
bitCount = 4
第13位 '0':
byte = 00000111 << 1 = 00001110
bitCount = 5
第14位 '1':
byte = 00001110 << 1 = 00011100
byte = 00011100 | 1 = 00011101
bitCount = 6
剩余6位,需要左移2位补0:
byte = 00011101 << 2 = 01110100
→ 保存字节 0x74
最终结果:[0xB3, 0x74]
Padding的计算
|
|
示例:
- 14位:
14 % 8 = 6→8 - 6 = 2→ padding = 2 - 16位:
16 % 8 = 0→8 - 0 = 8→8 % 8 = 0→ padding = 0 - 15位:
15 % 8 = 7→8 - 7 = 1→ padding = 1
字节转换为二进制字符串
算法思路:
- 遍历每个字节
- 对于普通字节,读取全部8位
- 对于最后一个字节,根据padding只读取有效位
代码实现:
|
|
图解转换过程
输入:[0xB3, 0x74],padding = 2
处理过程:
第1个字节 0xB3 = 10110011:
j=0: (0xB3 >> 7) & 1 = 1 → '1'
j=1: (0xB3 >> 6) & 1 = 0 → '0'
j=2: (0xB3 >> 5) & 1 = 1 → '1'
j=3: (0xB3 >> 4) & 1 = 1 → '1'
j=4: (0xB3 >> 3) & 1 = 0 → '0'
j=5: (0xB3 >> 2) & 1 = 0 → '0'
j=6: (0xB3 >> 1) & 1 = 1 → '1'
j=7: (0xB3 >> 0) & 1 = 1 → '1'
结果:10110011
第2个字节 0x74 = 01110100, padding=2:
只读取 8-2=6 位:
j=0: (0x74 >> 7) & 1 = 0 → '0'
j=1: (0x74 >> 6) & 1 = 1 → '1'
j=2: (0x74 >> 5) & 1 = 1 → '1'
j=3: (0x74 >> 4) & 1 = 1 → '1'
j=4: (0x74 >> 3) & 1 = 0 → '0'
j=5: (0x74 >> 2) & 1 = 1 → '1'
结果:011101(忽略最后2位0)
最终二进制字符串:10110011011101
测试代码
|
|
小结与练习
本章总结:
-
Huffman编码生成:
- 使用递归前序遍历生成编码
- 左0右1的约定
- 编码存储在 unordered_map 中
-
位运算基础:
- 掌握常用位运算符
- 理解位运算的性能优势
- 学会在实际问题中应用位运算
-
二进制与字节转换:
- 理解位与字节的转换过程
- 掌握padding的概念和计算
- 能够手动进行转换计算
练习1:位运算
写出以下表达式的结果:
|
|
练习2:手动转换
手动将以下二进制字符串转换为字节数组,并计算padding:
"10110""1111000011110000""1010101010101"
练习3:优化代码
如何优化 stringToBytes 函数,使其更高效?
练习4:理解编码
为什么高频字符的Huffman编码更短?这与树的结构有什么关系?
本节目录