13.python实现赫夫曼树和赫夫曼编码

赫夫曼树

  • 路径和路径长度:表示树从根节点开始到达节点经过的次数,若一颗树根节点为1层,那么第K层的树的路径的长度为K-1
  • 权: 赋予每一个节点上面特定的权重值
  • 带权路径:带权路径等于节点的权与路径长度的乘积,为带权路径 = 权 * 路径长度
  • 树的带权路径长度:为所有叶子节点的带权路径之和记做WPL(weight path length)

赫夫曼树huffman-tree或哈夫曼树,又称最优二叉树,如果一颗二叉树的带权路径之和最小,及权值越高的越靠近根节点的树为最优二叉树。

核心思想

  • 将一个序列从小到大排序,将每个数据看做一个节点,每个节点可以看做一个最简单的二叉树
  • 取出权重最小的2个节点构成一个最小的二叉树
  • 组成新的二叉树的新的节点的权重为2个节点权重之和
  • 移除2个旧节点,将新的二叉树节点重新加入序列中并进行从小到大排序
  • 循环一直到序列中只有一个数据即为根节点的时候就构成了赫夫曼树

赫夫曼编码

赫夫曼编码是一种变长编码方式,在通信中是一种经典的应用。广泛用于文件压缩,压缩率通常在20%~90%之间,主要通过使用的频率在最大化节约字符的存储空间。

实现步骤

  1. 统计各个字符的使用次数,用一个Node节点保存起来,具体data属性为字符对应的byte,weight权重代表他出现的次数
  2. 根据上述统计构建赫夫曼树
  3. 根据构建的赫夫曼树约定编码向左路径为0,右路径为1
  4. 单独创建一个全局的赫夫曼码表Map key为字符对应的byte String是该字符在这个赫夫曼树上的路径 由0和1拼接的字符串
  5. 递归赫夫曼树将所有叶子节点 记录节点的路径 添加赫夫曼码表中
  6. 如果要实现数据压缩,根据原文去赫夫曼码表中找对应的赫夫曼编码,拼接成一个新的编码,新生成的赫夫曼编码8位一个字节封装到一个byte数组中即可完成压缩
class HuffmanNode(object):
    def __init__(self, data, weight):
        self.data = data
        self.weight = weight
        self.left = None
        self.right = None

    def __gt__(self, other):
        return self.weight > other.weight

    def __ge__(self, other):
        return self.weight >= other.weight

    def __lt__(self, other):
        return self.weight < other.weight

    def __le__(self, other):
        return self.weight <= other.weight

    def __str__(self):
        return '(data=%s,weight=%d)' % (str(self.data), self.weight)

    def is_leaf(self):
        return self.left is None and self.right is None

    def pre_order(self):
        print(self, end='->')
        if self.left is not None:
            self.left.pre_order()
        if self.right is not None:
            self.right.pre_order()


class HuffmanTree(object):
    """
    - 路径和路径长度:表示树从根节点开始到达节点经过的次数,若一颗树根节点为1层,那么第K层的树的路径的长度为K-1
    - 权: 赋予每一个节点上面特定的权重值
    - 带权路径:带权路径等于节点的权与路径长度的乘积,为带权路径 = 权 * 路径长度
    - 树的带权路径长度:为所有**叶子节点**的带权路径之和记做WPL(weight path length)
    ### 核心思想
    赫夫曼树huffman-tree或哈夫曼树,又称最优二叉树,如果一颗二叉树的带权路径之和最小,及权值越高的越靠近根节点的树为最优二叉树。
    - 将一个序列从小到大排序,将每个数据看做一个节点,每个节点可以看做一个最简单的二叉树
    - 取出权重最小的2个节点构成一个最小的二叉树
    - 组成新的二叉树的新的节点的权重为2个节点权重之和
    - 移除2个旧节点,将新的二叉树节点重新加入序列中并进行从小到大排序
    - 循环一直到序列中只有一个数据即为根节点的时候就构成了赫夫曼树
    """

    def __init__(self, data):
        self.data = data

    def create_huffman_tree(self):
        if len(self.data):
            huffman_codes = []
            # 将数组生成huffmanNode 只保存了权重
            for i in self.data:
                huffman_codes.append(HuffmanNode(None, i))
            while len(huffman_codes) > 1:
                # 升序遍历
                huffman_codes = sorted(huffman_codes)
                # 取出最小2个节点
                left = huffman_codes.pop(0)
                right = huffman_codes.pop(0)
                # 组成新的二叉树的新的节点的权重为2个节点权重之和
                parent = HuffmanNode(None, left.weight + right.weight)
                parent.left = left
                parent.right = right
                # 移除2个旧节点,将新的二叉树节点重新加入序列中并进行从小到大排序
                huffman_codes.append(parent)
            return huffman_codes[0]


class HuffmanCode(object):
    def __init__(self, content):
        """
        1. 统计各个字符的使用次数,用一个Node节点保存起来,具体data属性为字符对应的byte,weight权重代表他出现的次数
        2. 根据上述统计构建赫夫曼树
        3. 根据构建的赫夫曼树约定编码向左路径为0,右路径为1
        4. 单独创建一个全局的赫夫曼码表Map key为字符对应的byte String是该字符在这个赫夫曼树上的路径 由0和1拼接的字符串
        5. 递归赫夫曼树将所有叶子节点 记录节点的路径 添加赫夫曼码表中
        6. 如果要实现数据压缩,根据原文去赫夫曼码表中找对应的赫夫曼编码,拼接成一个新的编码,新生成的赫夫曼编码8位一个字节封装到一个byte数组中即可完成压缩
        :param code: 需要进行赫夫曼编码的原始字符串
        """
        self.content = content
        self.codes_dict = {}
        self.huffman_codes = []
        for c in list(content):
            if self.codes_dict.get(c) is None:
                self.codes_dict[c] = 1
            else:
                self.codes_dict[c] = self.codes_dict[c] + 1
        #  1. 统计各个字符的使用次数,用一个Node节点保存起来,具体data属性为字符对应的byte,weight权重代表他出现的次数
        for item in self.codes_dict.items():
            self.huffman_codes.append(HuffmanNode(item[0], item[1]))

    def create_huffman_tree(self):
        """
        创建赫夫曼树
        :return:
        """
        while len(self.huffman_codes) > 1:
            left = self.huffman_codes.pop(0)
            right = self.huffman_codes.pop(0)
            parent = HuffmanNode(None, left.weight + right.weight)
            parent.left = left
            parent.right = right
            self.huffman_codes.append(parent)
        return self.huffman_codes[0]

    def create_huffman_code(self):
        """
        生成赫夫曼编码
        :return:
        """
        # 根据上述统计构建赫夫曼树
        huffman_tree = self.create_huffman_tree()
        # 3. 根据构建的赫夫曼树约定编码向左路径为0,右路径为1
        # 4. 单独创建一个全局的赫夫曼码表Map key为字符对应的byte String是该字符在这个赫夫曼树上的路径 由0和1拼接的字符串
        huffman_table = {}
        # 5. 递归赫夫曼树将所有叶子节点 记录节点的路径 添加赫夫曼码表中
        self.get_huffman_table(huffman_tree, '', huffman_table)
        # 6. 如果要实现数据压缩,根据原文去赫夫曼码表中找对应的赫夫曼编码,拼接成一个新的编码,新生成的赫夫曼编码8位一个字节封装到一个byte数组中即可完成压缩
        return self.get_huffman_zip_code(huffman_table), huffman_table

    def get_huffman_zip_code(self, huffman_table):
        """
        根据生成的赫夫曼码表将原文变成赫夫曼编码之后的数据
        :param content:
        :param huffman_table:
        :return:
        """
        code = ''
        for c in list(self.content):
            code += huffman_table[c]
        return code

    def get_huffman_table(self, node, code, huffman_table):
        if node.data is None:
            # 非叶子节点
            self.get_huffman_table(node.left, code + '0', huffman_table)
            self.get_huffman_table(node.right, code + '1', huffman_table)
        else:
            huffman_table[node.data] = code

    def parse_huffman_code(self, code, huffman_table):
        content = ''
        temp = ''
        # huffman_table = {v: k for k, v in huffman_table.items()}
        huffman_table = dict(zip(huffman_table.values(), huffman_table.keys()))
        for i in range(
                len(code)):
            temp += code[i]
            if huffman_table.get(temp) is not None:
                content += huffman_table.get(temp)
                temp = ''
        return content


if __name__ == '__main__':
    # 测试赫夫曼树
    # arr = [13, 7, 8, 3, 29, 6, 1]
    # tree = HuffmanTree(arr)
    # huffman_node = tree.create_huffman_tree()
    # huffman_node.pre_order()
    # 测试赫夫曼编码
    huffman_code = HuffmanCode('i like like like java do you like a java')
    code, huffman_table = huffman_code.create_huffman_code()
    print(code)
    org_content = huffman_code.parse_huffman_code(code, huffman_table)
    print(org_content)

你可能感兴趣的:(python数据结构算法)