【数据结构与算法基础】树的应用

写在前面

树这一数据结构学的差不多了,该拉出来练练了。本节学习几个树的应用,包括优先队列、Huffman编码等。

 

1.优先队列(Priority Queue)

优先队列是特殊的“队列”,取出元素的顺序是依照元素的优先权大小,而不是元素进入队列的顺序。优先队列要求保证“最优先元素先出”的原则。优先队列支持两种操作:删除最大元素(或最小元素)和插入元素。我们将看到,删除最大元素的方法可以很简单地转换成删除最小元素。

优先队列的简单实现

 

  • 基于数组实现
操作 描述 复杂度
插入 元素总是插入尾部 O(1)
删除

查找最大(最小)关键字

从数组中删去需要移动元素

O(n)
  • 基于链表实现

 

操作 描述 复杂度
插入 元素总是插入链表的头部 O(1)
删除 查找最大(最小)关键字
从数组中删去需要移动元素

O(n)

O(1)

 

  • 有序数组实现

 

操作 描述 复杂度
插入

找到合适的位置

移动元素并插入

O(n)或O(log n)

O(n)

删除 删去最后一个元素

O(1)

  • 有序链表实现
操作 描述 复杂度
插入 找到合适的位置
插入

O(n)

O(1)

删除 删除首元素或最后元素 O(1)

2.堆

从上述优先队列的实现可以看出,最坏情况下他们的复杂度都会达到O(N)。为了优化性能,考虑到前面学过的二叉树的数据结构,利用堆实现优先队列。堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。

对于任意位置i,其左儿子在数组中位置为2i,其右儿子在数组中位置为2i+1,其父亲位置在floor(i/2)。 

主要有两种不同的基本堆:最大堆和最小堆。

在最大堆中,任一结点的值小于或等于其子节点的值;最小堆则反之。

2.1 最小堆的操作

类型名称:最小堆(MinHeap)

数据对象集:一个有N>0个元素的最大堆H是一颗完全二叉树,每个结点上的元素值不小于其子节点元素的值。

操作集

 

build_min_heap 建立最小堆
insert 插入一个值,并且调整使满足堆结构
delect 删除
min_heapify 最小堆化:使以i为根的子树成为最小堆

下面具体看看几个重要的操作。

2.2 最小堆的插入

【数据结构与算法基础】树的应用_第1张图片

如下是代码实现

def INSERT(self, val):
        # 插入一个值val,并且调整使满足堆结构
        self.items.append(val)
        idx = len(self.items) - 1
        parIdx = self.PARENT(idx)
        while parIdx >= 0:
            if self.items[parIdx] > self.items[idx]:
                self.items[parIdx], self.items[idx] = self.items[idx], self.items[parIdx]
                idx = parIdx
                parIdx = self.PARENT(parIdx)
            else:
                break
        self.heapsize += 1

2.3 最小堆的删除

取出根结点元素,同时删除堆的一个结点。

【数据结构与算法基础】树的应用_第2张图片

    def DELETE(self):
        last = len(self.items) - 1
        if last < 0:
            # 堆为空
            return None
        # else:
        self.items[0], self.items[last] = self.items[last], self.items[0]
        val = self.items.pop()
        self.heapsize -= 1
        self.MIN_HEAPIFY(0)
        return val

2.4 最小堆的建立

将已经存在的N个元素按最小堆的要求存放在一个一维数组中。

 

以上操作的python实现已上传至Github:data_structure/Tree

 

3.哈夫曼树

先了解哈夫曼树的一些基本术语

 

  • 路径和路径长度:树中一个结点到另一个结点之间的分支构成这两个结点之间的路径;路径上的分枝数目称作路径长度,它等于路径上的结点数减1.
  • 结点的权和带权路径长度:在许多应用中,常常将树中的结点赋予一个有着某种意义的实数,我们称此实数为该结点的权;结点的带权路径长度规定为从树根结点到该结点之间的路径长度与该结点上权的乘积.
  • 树的带权路径长度:为树中所有叶子结点的带权路径长度之和,公式为: 

【数据结构与算法基础】树的应用_第3张图片

 

  • 哈夫曼树:哈夫曼树又称最优二叉树。它是 n 个带权叶子结点构成的所有二叉树中,带权路径长度 WPL 最小的二叉树。如下图为一哈夫曼树示意图。

【数据结构与算法基础】树的应用_第4张图片

3.1 哈夫曼树的构造

根据哈弗曼树的定义,一棵二叉树要使其WPL值最小,必须使权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。

【数据结构与算法基础】树的应用_第5张图片

【数据结构与算法基础】树的应用_第6张图片

3.2 哈夫曼树的特点

 

  • 没有度为1的结点;
  • n个叶子结点的哈夫曼树共有2n-1个结点;
  • 哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树
  • 对于同一组给定权值叶结点所构造的哈夫曼树,树的形状可能不同
  • 哈夫曼算法的复杂度:O(log N)

3.3哈夫曼编码

哈夫曼编码主要用于数据压缩。哈夫曼编码是一种可变长编码。该编码将出现频率高的字符,使用短编码;将出现频率低的字符,使用长编码。变长编码的主要问题是,必须实现非前缀编码,即在一个字符集中,任何一个字符的编码都不是另一个字符编码的前缀。如:0、10就是非前缀编码,而0、01不是非前缀编码。

问题:给定一段字符串,如何对字符进行编码,可以使得该字符串的编码存储空间最小?

 

解决

1. 利用字符集中每个字符的使用频率作为权值构造一个哈夫曼树; 

2. 从根结点开始,为到每个叶子结点路径上的左分支赋予0,右分支赋予1,并从根到叶子方向形成该叶子结点的编码.

实例:假设一个文本文件TFile中只包含7个字符{a,b,c,d,e,f},这7个字符在文本中出现的次数为{9,12,6,3,5,15} 
通过哈夫曼树来构造的编码称为哈弗曼编码(huffman code)

【数据结构与算法基础】树的应用_第7张图片

所以:a 的编码为:00 
          b 的编码为:01 
          c 的编码为:100 
          d 的编码为:1010 
          e 的编码为:1011 

          f 的编码为:11

3.4 哈夫曼树及编码的Python实现

  

代码实现已上传至Github:data_structure/Tree/Huffman tree

 

以上~

2018.04.29

 

 

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