哈夫曼树
一、概念
哈夫曼树又称最优树,是一类带权路径长度最短的树。
二、实现
哈夫曼树最经典的应用就是根据每种字符在单词中出现的频率构造一棵哈夫曼树,使用它对文章进行编码、解码,从而使得文章的二进制编码最短。下面就以此问题为例,讲解哈夫曼树的实现。此问题主要需要支持以下三个方法:
1. 构造哈夫曼树
(1)将所有需要编码的字符及其出现频率作为树节点,其中字符为节点数据,出现频率为节点权值,构造n棵只有根节点的二叉树。
(2)在森林中选取两棵根节点权值最小的树作为左右子树,构造一棵新的二叉树,其根节点权值为左右子树根节点的权值之和。
(3)在森林中删除这两棵树,同时将得到的新的二叉树加入森林中。
(4)重复(2)和(3),直到森林中只剩下一棵树为止,这棵树即为哈夫曼树。
可以使用最小堆来存储森林这个集合,便于选取根节点权值最小的树。
2. 编码
(1)找到需要编码的字符,对应哈夫曼树某个叶子节点的位置。
(2)向上找父节点,做出判断,若当前节点为父节点的左子节点,则编码为‘0’,若是右子节点则编码为‘1’。
(3)重复(2),直到哈夫曼树根节点。
(4)将所得到的编码串反序输出,即为当前字符的二进制编码。
基于(1),哈夫曼树的叶子结点应该支持便捷的遍历,所以本文使用数组来作为哈夫曼树的存储结构,当然也可以使用链式存储,但是这样就需要在所有叶子节点间加多一条链表,方便遍历。
可以利用栈的先进后出的特性来将编码串进行反序输出。
3. 解码
(1)从哈夫曼树根节点出发,遇‘0’则向左遍历,遇到‘1’则向右遍历。
(2)若到达哈夫曼树的叶子节点,则将叶子节点表示的字符读出,然后重新执行(1)。
(3)重复(1)和(2),直至编码读取完毕,得到的所有字符连接起来则是编码对应的字符串。
下面给出Go语言的实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | /** * 哈夫曼树 * author:JetWu * date:2020.02.09 */ package huffmanTree import ( "errors" "fmt" ) /** * 哈夫曼树节点结构 **/ type HuffmanTreeNode struct { data byte //数据 weight int //权重 index int //当前节点索引 parent int //父节点索引 left int //左子节点索引 right int //右子节点索引 } /** * 哈夫曼树结构 **/ type HuffmanTree struct { slice []*HuffmanTreeNode //节点数组,最后一个元素即是根节点 size int //叶子节点个数 } /** * 构造哈夫曼树 **/ func CreateHuffmanTree(data []byte, weight []int) *HuffmanTree { if len(data) < 2 || len(data) != len(weight) { return nil } count := len(data) hft := &HuffmanTree{ slice: make([]*HuffmanTreeNode, count), size: count, } mh := NewMinHeap() for i := 0; i < len(data); i++ { //将所有节点放入最小堆 htNode := &HuffmanTreeNode{ data: data[i], weight: weight[i], index: i, parent: -1, left: -1, right: -1, } hft.slice[i] = htNode mh.Insert(htNode) } for mh.Count() > 1 { //每次取出权重最小的两个节点,合并成树,将树根节点插入最小堆 htNode1, _ := mh.RemoveMin() htNode2, _ := mh.RemoveMin() index := len(hft.slice) htNode := &HuffmanTreeNode{ weight: htNode1.weight + htNode2.weight, index: index, parent: -1, left: htNode1.index, right: htNode2.index, } hft.slice = append(hft.slice, htNode) htNode1.parent, htNode2.parent = index, index mh.Insert(htNode) } return hft } /** * 打印所有字符及其对应编码 **/ func (hft *HuffmanTree) Print() { b := make([]byte, 1) for i := 0; i < hft.size; i++ { fmt.Print(string(hft.slice[i].data), ": " ) b[0] = hft.slice[i].data code, _ := hft.Encode(string(b)) fmt.Println(code) } } /** * 打印所有节点信息 **/ func (hft *HuffmanTree) Print2() { for _, d := range hft.slice { fmt.Println( "data:" , string(d.data), ", weight:" , d.weight, ", index:" , d.index, ", parent:" , d.parent, ", left:" , d.left, ", right:" , d.right) } } /** * 编码 **/ func (hft *HuffmanTree) Encode(data string) (string, error) { if len(data) == 0 { return "" , errors.New( "数据为空" ) } if hft == nil { return "" , errors.New( "哈弗曼树为空" ) } bData := []byte(data) var code []byte as := NewArrayStack() for _, d := range bData { found := false for i := 0; i < hft.size; i++ { if hft.slice[i].data == d { found = true ptr := hft.slice[i] lastIndex := ptr.index for ptr.parent != -1 { ptr = hft.slice[ptr.parent] if ptr.left == lastIndex { //左0 as.Push( '0' ) } else { //右1 as.Push( '1' ) } lastIndex = ptr.index } } } if !found { return "" , errors.New( "编码不存在:" + string(d)) } for !as.IsEmpty() { //先进后出,达到倒序效果 b, _ := as.Pop() code = append(code, b) } } return string(code), nil } /** * 解码 **/ func (hft *HuffmanTree) Decode(code string) (string, error) { if len(code) == 0 { return "" , errors.New( "编码为空" ) } if hft == nil { return "" , errors.New( "哈弗曼树为空" ) } bCode := []byte(code) var data []byte root := hft.slice[len(hft.slice)-1] //根节点 ptr := root for _, c := range bCode { if c == '0' { ptr = hft.slice[ptr.left] } else if c == '1' { ptr = hft.slice[ptr.right] } else { return "" , errors.New( "编码错误:" + string(c)) } if ptr.left == -1 && ptr.right == -1 { //叶子节点 data = append(data, ptr.data) ptr = root } } if ptr != root { return "" , errors.New( "编码错误" ) } return string(data), nil } /** 以下为哈夫曼树构造过程使用的最小堆 **/ /** * 最小堆结构 **/ type MinHeap struct { slice []*HuffmanTreeNode size int //最小堆大小 } /** * 创建一个空最小堆 **/ func NewMinHeap() *MinHeap { return &MinHeap{ slice: make([]*HuffmanTreeNode, 10), size: 0, } } /** * 最小堆容量 **/ func (mh *MinHeap) Count() int { return mh.size } /** * 向下调整(最小堆初始化、删除时调用) **/ func (mh *MinHeap) shiftDown(start int) { i, j := start, start*2+1 //j为i的左子节点 temp := mh.slice[i] for j < mh.size { if j < mh.size-1 && mh.slice[j+1].weight < mh.slice[j].weight { //比较获得左右子节点的最小值编号 j++ } if temp.weight <= mh.slice[j].weight { //左右子节点都比父节点大 break } else { //继续向下比较 mh.slice[i] = mh.slice[j] i, j = j, j*2+1 } } mh.slice[i] = temp } /** * 向上调整(最小堆插入时调用) **/ func (mh *MinHeap) siftUp(start int) { if start > mh.size-1 { return } j, i := start, (start-1)/2 //i为j的父节点 temp := mh.slice[j] for j > 0 { if temp.weight >= mh.slice[i].weight { //子节点大于父节点 break } else { mh.slice[j] = mh.slice[i] j, i = i, (i-1)/2 } } mh.slice[j] = temp } /** * 插入 **/ func (mh *MinHeap) Insert(data *HuffmanTreeNode) { //将插入元素放置在最后节点 mh.size++ if mh.size <= len(mh.slice) { mh.slice[mh.size-1] = data } else { mh.slice = append(mh.slice, data) } //向上调整 mh.siftUp(mh.size - 1) } /** * 删除 **/ func (mh *MinHeap) RemoveMin() (*HuffmanTreeNode, error) { if mh.size < 1 { return nil, errors.New( "最小堆为空" ) } min := mh.slice[0] mh.size-- //使用最后一个节点替换第一个节点 mh.slice[0] = mh.slice[mh.size] //向下调整 mh.shiftDown(0) return min, nil } /** 以下为哈夫曼树编码过程使用的栈 **/ /** * 数组栈结构 **/ type ArrayStack struct { slice []byte head int //栈顶位置:slice最后一个有效元素的索引位置 } /** * 创建空栈 **/ func NewArrayStack() *ArrayStack { return &ArrayStack{ slice: make([]byte, 10), head: -1, } } /** * 判断栈是否为空 **/ func (as *ArrayStack) IsEmpty() bool { return as.head == -1 } /** * 入栈 **/ func (as *ArrayStack) Push(data byte) bool { as.head++ if as.head+1 <= len(as.slice) { as.slice[as.head] = data } else { as.slice = append(as.slice, data) } return true } /** * 出栈 **/ func (as *ArrayStack) Pop() (byte, error) { if as.head == -1 { return 0, errors.New( "栈为空" ) } data := as.slice[as.head] as.head-- return data, nil } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)