算法第三次作业-哈夫曼编码压缩文件

一、运行环境:

Win7、python3.7

二、运行过程说明:

数据文件格式:txt、docx、csv、doc、pdf、xls等任意格式的文件。

输入格式:首先,在cmd中进入代码所在的目录;然后,输入python 0/1 被压缩文件名称 压缩后的文件名称 。(其中0代表压缩,1代表解压缩)

本次数据:

 

操作(以文件1为例):

压缩文件:python main.py 0 input_assgin01_01.txt out1.txt

 

 

解压缩文件:python main.py 1 input_assgin01_01.txt out1.txt

 

输出:

 

三、算法设计

3.1问题描述:

利用Huffman编码对文本文件进行压缩与解压。

输入:一个文本文件;

输出:压缩后的文件。

算法过程:

(1)统计文本文件中每个字符的使用频度;

(2)构造huffman编码;

(3)以二进制流形式压缩文件。

3.2解题思路:

(1)压缩

Step1:对需要被压缩的文件以二进制的方式读,然后以byte为单位读取里面的值,然后将其转换为int值,一共有[0-255]256个值,遍历文件中的所有byte,统计出现每个值的次数,作为我们后面叶节点里面额的weight(权重 );

Step2:使用上面的[0-255]每个值及其对应的权重,构造对应的Huffman编码树,然后分配[0-255]中每个值对应的Huffman编码;

Step3:在压缩文件的开始,将[0-255]及其对应的weight(权重)以二进制的方式保存到压缩文件当中,此举的目的是方便下次解压缩时根据原文件的[0-255]出现的频率去构造相应的huffman树,然后对压缩文件进行解压缩的操作;

Step4:遍历文件中的每一个byte,然后以Step2涨生成的Huffman编码,将该子街转换为Huffman编码对应的01组合;

Step5:将Step中的生成的01组合串,每8位为一个单位,将其转换为相应的byte,然后以二进制的方式写入到压缩文件当中,这样压缩就完成了。

(2)解压缩

Step1:以二进制方式读出压缩文件中保存的被压缩文件中[0-255]每个值出现频率;

Step2:使用上面[0-255]每个值及其对应的权重,构造对应的Huffman编码树,然后分配[0-255]中每一个值对应的Huffman编码;

Step3:接着以二进制的方式读取压缩文件的内容,独处二进制的01串;

Step4:使用Step3读取出来的01串在Step2中够早的Huffman编码树当中进行解压缩,进行解压锁的方法是:

1)最开始curr指针指向编码树的root;

2)循环取出01串的首位;

3)如果取出的值是0,curr指针指向左孩子;

4)如果取出的值是1,curr指针指向右孩子;

5)判断curr现在所指向的是否是叶节点:如果是,将该叶节点对应的字符写入到解压缩文件中,并且curr重置为编码树的root;如果不是,继续2,3,4的操作;

6)直到01串被取完,此时解压缩的工作完毕。

(3)对于如何借助Huffman树创建Huffman编码

Huffman编码将给字母分配编码。每个字母的编码的长度取决于在被压缩文件中对应字母的出现频率,称之为权重(weight)。每个字母的Huffman编码是从Huffman编码树的满二叉树(所有节点要么有左右两个子孩子,要么就没有子孩子)中得到的。Huffman编码树的每一个叶节点对应于一个字母,叶节点的权重 (weight)就是它对应的字母出现的频率。使用权重的目的是建立的Huffman编码树有最小外部路径权重WPL。

 

Step1:创建n个初始化的Huffman树,每个树只包含单一的叶节点,叶节点纪录对应的字母和该字母出现的频率(weight);

Step2:按照weight从小到大对其进行所有的Huffman树进行排序,取出其中weight最小的两棵树,构造一个新的Huffman树,新的Huffman树的weight等于两棵子树的weight之和,然后在原来的Huffman数组中删除这两棵树,并把新的Huffman树加入到原来的Huffman树数组当中;

Step3:反复上面的2中的操作,直到该数组当中只剩下一棵Huffman树,那么最后剩下来的那棵Huffman树就是我们构造好的Huffman编码树;

得到上面的Huffman编码树之后,就可以得到每个字符对应的编码了,方法是:从根节点找到该叶节点,如果向左子树前进一步,那么code + = '0',如果向右子树前进了一步,那么code+= '1',等到达该叶节点,code对应的内容,就是该叶节点对应字符的编码。

3.3数据结构的选择:

(1)节点虚类HuffNode。包含两个虚方法:1)获取节点的权重函数get_weight;2)判断此节点是否是叶节点的函数isleaf。

(2)树叶节点类LeafNode。包含方法: 1)初始化树节点的函数_init_;2)基类的方法isleaf;3)积累的方法get_weight;3)获取叶子节点的字符的值get_value。

(3)中间节点类IntlNode。包含方法:1)初始化中间节点的函数_init_;2)积累的方法isleaf;3)基类的方法get_weight;4)获取左孩子的函数get_left;5)获取右孩子的函数get_right。

(4)Huffman树类HuffTree。包含方法:1)初始化函数_init_;2)获取Huffman树的根节点的函数get_root;3)获取Huffman树的根节点的权重get_weight;4)便利Huffman_tree的函数traverse_huffman_tree。

(5)构造Huffman树的函数buildHuffmanTree。

四、算法详细描述:

4.1步骤:

Step1:实现Huffman编码树及其构造方法;

Step2:实现压缩文件函数compress();

Step3:实现解压缩文件函数decompress()。

4.2代码:

1Huffman编码树及其构造方法:

class HuffNode(object):

    """

    定义一个HuffNode虚类,里面包含两个虚方法:

    1. 获取节点的权重函数

    2. 获取此节点是否是叶节点的函数

    """

    def get_wieght(self):

        raise NotImplementedError(

            "The Abstract Node Class doesn't define 'get_wieght'")

    def isleaf(self):

        raise NotImplementedError(

            "The Abstract Node Class doesn't define 'isleaf'")

class LeafNode(HuffNode):

    """

    树叶节点类

    """

    def __init__(self, value=0, freq=0,):

        """

        初始化 树节点 需要初始化的对象参数有 :value及其出现的频率freq

        """

        super(LeafNode, self).__init__()

        # 节点的值

        self.value = value

        self.wieght = freq

    def isleaf(self):

        """

        基类的方法,返回True,代表是叶节点

        """

        return True

    def get_wieght(self):

        """

        基类的方法,返回对象属性 weight,表示对象的权重

        """

        return self.wieght

    def get_value(self):

        """

        获取叶子节点的 字符 的值

        """

        return self.value

class IntlNode(HuffNode):

    """

    中间节点类

    """

    def __init__(self, left_child=None, right_child=None):

        """

        初始化 中间节点 需要初始化的对象参数有 :left_child, right_chiled, weight

        """

        super(IntlNode, self).__init__()

        # 节点的值

        self.wieght = left_child.get_wieght() + right_child.get_wieght()

        # 节点的左右子节点

        self.left_child = left_child

        self.right_child = right_child

    def isleaf(self):

        """

        基类的方法,返回False,代表是中间节点

        """

        return False

    def get_wieght(self):

        """

        基类的方法,返回对象属性 weight,表示对象的权重

        """

        return self.wieght

    def get_left(self):

        """

        获取左孩子

        """

        return self.left_child

    def get_right(self):

        """

        获取右孩子

        """

        return self.right_child

class HuffTree(object):

    """

    huffTree

    """

    def __init__(self, flag, value =0, freq=0, left_tree=None, right_tree=None):

        super(HuffTree, self).__init__()

        if flag == 0:

            self.root = LeafNode(value, freq)

        else:

            self.root = IntlNode(left_tree.get_root(), right_tree.get_root())

    def get_root(self):

        """

        获取huffman tree 的根节点

        """

        return self.root

    def get_wieght(self):

        """

        获取这个huffman树的根节点的权重

        """

        return self.root.get_wieght()

    def traverse_huffman_tree(self, root, code, char_freq):

        """

        利用递归的方法遍历huffman_tree,并且以此方式得到每个 字符 对应的huffman编码

        保存在字典 char_freq中

        """

        if root.isleaf():

            char_freq[root.get_value()] = code

            print ("it = %c  and  freq = %d  code = %s")%(chr(root.get_value()),root.get_wieght(), code)

            return None

        else:

            self.traverse_huffman_tree(root.get_left(), code+'0', char_freq)

            self.traverse_huffman_tree(root.get_right(), code+'1', char_freq)

def buildHuffmanTree(list_hufftrees):

    """

    构造huffman树

    """

    while len(list_hufftrees) >1 :

        # 1. 按照weight 对huffman树进行从小到大的排序

        list_hufftrees.sort(key=lambda x: x.get_wieght())

        # 2. 跳出weight 最小的两个huffman编码树

        temp1 = list_hufftrees[0]

        temp2 = list_hufftrees[1]

        list_hufftrees = list_hufftrees[2:]

        # 3. 构造一个新的huffman树

        newed_hufftree = HuffTree(1, 0, 0, temp1, temp2)

        # 4. 放入到数组当中

        list_hufftrees.append(newed_hufftree)

    # last.  数组中最后剩下来的那棵树,就是构造的Huffman编码树

    return list_hufftrees[0]

2)压缩文件函数: 

        def compress(inputfilename, outputfilename):

    """

    压缩文件,参数有

    inputfilename:被压缩的文件的地址和名字

    outputfilename:压缩文件的存放地址和名字

    """

    #1. 以二进制的方式打开文件

    f = open(inputfilename,'rb')

    filedata = f.read()

    # 获取文件的字节总数

    filesize = f.tell()

    # 2. 统计 byte的取值[0-255] 的每个值出现的频率

    # 保存在字典 char_freq中

    char_freq = {}

    for x in range(filesize):

        # tem = six.byte2int(filedata[x]) #python2.7 version

        tem = filedata[x] # python3.0 version

        if tem in char_freq.keys():

            char_freq[tem] = char_freq[tem] + 1

        else:

            char_freq[tem] = 1

    # 输出统计结果

    for tem in char_freq.keys():

        print(tem,' : ',char_freq[tem])

    # 3. 开始构造原始的huffman编码树 数组,用于构造Huffman编码树

    list_hufftrees = []

    for x in char_freq.keys():

        # 使用 HuffTree的构造函数 定义一棵只包含一个叶节点的Huffman树

        tem = HuffTree(0, x, char_freq[x], None, None)

        # 将其添加到数组 list_hufftrees 当中

        list_hufftrees.append(tem)

    # 4. Step2中获取到的 每个值出现的频率的信息

    # 4.1. 保存叶节点的个数

    length = len(char_freq.keys())

    output = open(outputfilename, 'wb')

    # 一个int型的数有四个字节,所以将其分成四个字节写入到输出文件当中

    a4 = length&255

    length = length>>8

    a3 = length&255

    length = length>>8

    a2 = length&255

    length = length>>8

    a1 = length&255

    output.write(six.int2byte(a1))

    output.write(six.int2byte(a2))

    output.write(six.int2byte(a3))

    output.write(six.int2byte(a4))

    # 4.2  每个值 及其出现的频率的信息

    # 遍历字典 char_freq

    for x in char_freq.keys():

        output.write(six.int2byte(x))

        #

        temp = char_freq[x]

        # 同样出现的次数 是int型,分成四个字节写入到压缩文件当中

        a4 = temp&255

        temp = temp>>8

        a3 = temp&255

        temp = temp>>8

        a2 = temp&255

        temp = temp>>8

        a1 = temp&255

        output.write(six.int2byte(a1))

        output.write(six.int2byte(a2))

        output.write(six.int2byte(a3))

        output.write(six.int2byte(a4))

    # 5. 构造huffman编码树,并且获取到每个字符对应的 编码

    tem = buildHuffmanTree(list_hufftrees)

    tem.traverse_huffman_tree(tem.get_root(),'',char_freq)

    # 6. 开始对文件进行压缩

    code = ''

    for i in range(filesize):

        # key = six.byte2int(filedata[i]) #python2.7 version

        key = filedata[i] #python3 version

        code = code + char_freq[key]

        out = 0

        while len(code)>8:

            for x in range(8):

                out = out<<1

                if code[x] == '1':

                    out = out|1

            code = code[8:]

            output.write(six.int2byte(out))

            out = 0

    # 处理剩下来的不满8位的code

    output.write(six.int2byte(len(code)))

    out = 0

    for i in range(len(code)):

        out = out<<1

        if code[i]=='1':

            out = out|1

    for i in range(8-len(code)):

        out = out<<1

    # 把最后一位给写入到文件当中

    output.write(six.int2byte(out))

    # 6. 关闭输出文件,文件压缩完毕

    output.close()

3)解压缩文件函数:

def decompress(inputfilename, outputfilename):

    """

    解压缩文件,参数有

    inputfilename:压缩文件的地址和名字

    outputfilename:解压缩文件的存放地址和名字

    """

    # 读取文件

    f = open(inputfilename,'rb')

    filedata = f.read()

    # 获取文件的字节总数

    filesize = f.tell()

    # 1. 读取压缩文件中保存的树的叶节点的个数

    # 一下读取 4个 字节,代表一个int型的值

    # python2.7 version

    # a1 = six.byte2int(filedata[0])

    # a2 = six.byte2int(filedata[1])

    # a3 = six.byte2int(filedata[2])

    # a4 = six.byte2int(filedata[3])

    # python3 version

    a1 = filedata[0]

    a2 = filedata[1]

    a3 = filedata[2]

    a4 = filedata[3]   

    j = 0

    j = j|a1

    j = j<<8

    j = j|a2

    j = j<<8

    j = j|a3

    j = j<<8

    j = j|a4

    leaf_node_size = j

    # 2. 读取压缩文件中保存的相信的原文件中 [0-255]出现的频率的信息

    # 构造一个 字典 char_freq 一遍重建 Huffman编码树

    char_freq = {}

    for i in range(leaf_node_size):

        # c = six.byte2int(filedata[4+i*5+0]) # python2.7 version

        c = filedata[4+i*5+0] # python3 vesion

        # 同样的,出现的频率是int型的,读区四个字节来读取到正确的数值

        #python3

        a1 = filedata[4+i*5+1]

        a2 = filedata[4+i*5+2]

        a3 = filedata[4+i*5+3]

        a4 = filedata[4+i*5+4]

        j = 0

        j = j|a1

        j = j<<8

        j = j|a2

        j = j<<8

        j = j|a3

        j = j<<8

        j = j|a4

        print(c, j)

        char_freq[c] = j

    # 3. 重建huffman 编码树,和压缩文件中建立Huffman编码树的方法一致

    list_hufftrees = []

    for x in char_freq.keys():

        tem = HuffTree(0, x, char_freq[x], None, None)

        list_hufftrees.append(tem)

    tem = buildHuffmanTree(list_hufftrees)

    tem.traverse_huffman_tree(tem.get_root(),'',char_freq)

    # 4. 使用Step3中重建的huffman编码树,对压缩文件进行解压缩

    output = open(outputfilename, 'wb')

    code = ''

    currnode = tem.get_root()

    for x in range(leaf_node_size*5+4,filesize):

        #python3

        c = filedata[x]

        for i in range(8):

            if c&128:

                code = code +'1'

            else:

                code = code + '0'

            c = c<<1

        while len(code) > 24:

            if currnode.isleaf():

                tem_byte = six.int2byte(currnode.get_value())

                output.write(tem_byte)

                currnode = tem.get_root()

            if code[0] == '1':

                currnode = currnode.get_right()

            else:

                currnode = currnode.get_left()

            code = code[1:]

    # 4.1 处理最后 24位

    sub_code = code[-16:-8]

    last_length = 0

    for i in range(8):

        last_length = last_length<<1

        if sub_code[i] == '1':

            last_length = last_length|1

    code = code[:-16] + code[-8:-8 + last_length]

    while len(code) > 0:

            if currnode.isleaf():

                tem_byte = six.int2byte(currnode.get_value())

                output.write(tem_byte)

                currnode = tem.get_root()

            if code[0] == '1':

                currnode = currnode.get_right()

            else:

                currnode = currnode.get_left()

            code = code[1:]

    if currnode.isleaf():

        tem_byte = six.int2byte(currnode.get_value())

        output.write(tem_byte)

        currnode = tem.get_root()

    # 4. 关闭文件,解压缩完毕

    output.close()

五、算法分析

(1)生成效率有效长度为n

同步数据,时间复杂度为:O(n);

生成树,时间复杂度为:O(n-1);

生成压缩码,生成单个密码时间复杂度为:O(log(n));

故总密码生成效率为:O(n*log(n));

生成排序树,由于字符按频数排序,故字符码可视为基本随机。因此,易知平均时间复杂度为:O(n*log(n));

总效率为: n+n-1+2*O(n*log(n))=O(n*log(n))

(2)压缩解码效率(总长为m,生成频数链表有效长度为n的文本)

压缩效率:对单字的平均效率为二叉排序树效率,平均为O(log(n)),故总效率为O(m*log(n));

解码效率:对单字的解密效率同样为O(log(n)),故总效率为O(m*log(n));

总效率:由于m>>n本算法总效率为O(m*log(n)),远高于无二叉排序树加速时的期望效率O(m*n)。当m、n有至少一个较大时有较大提升,m、n都较大时有机可观的效率提升,而仅多占用2个额外空间,优势明显。

(3)加速策略:本算法通过对哈夫曼树底层重排,以2个额外空间为代价,借助二叉排序树极大的提升了压缩效率,同时维持解码效率不变。实现了加速以及性能提升。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值