哈夫曼树

一、概念
哈夫曼树又称最优树,是一类带权路径长度最短的树。

二、实现
哈夫曼树最经典的应用就是根据每种字符在单词中出现的频率构造一棵哈夫曼树,使用它对文章进行编码、解码,从而使得文章的二进制编码最短。下面就以此问题为例,讲解哈夫曼树的实现。此问题主要需要支持以下三个方法:
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
}

  

posted @   疯一样的狼人  阅读(771)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示