Coursera北大《数据结构基础》之二叉树

本文基于Coursera北大课程《数据结构基础》,所有文中非标注图片均来自课件,侵删

目录

1. 二叉树(binary tree)概念

1.1 二叉树定义

1.2 二叉树的五种形态

1.3 二叉树相关术语

1.4 几种特殊的二叉树

1.4.1 满二叉树

1.4.2 完全二叉树

1.4.3 扩充二叉树

1.4.4 表达式二叉树

1.5 二叉树主要性质

2. 二叉树的抽象数据类型

2.1 深度优先搜索(DFS)

2.1.1 前序遍历(preorder traversal)

2.1.2 中序遍历(inorder traversal)

2.1.3 后序遍历(postorder traversal)

2.1.4 例子

2.2 宽度优先搜索(BFS)

3. 二叉树的存储结构

3.1 一般存储结构

3.2 完全二叉树存储结构

4. 二叉搜索树(Binary Search Tree, BST)

4.1 定义

4.2 二叉搜索树性质

5. 堆与优先队列

5.1 堆的定义(以最小堆为例)

5.2 堆的性质

5.3 对最小堆用筛选法siftdown调整

5.4 最小堆结点的插入和删除

5.4.1 最小堆插入结点

5.4.2 最小堆结点删除

5.5 优先队列

6. Huffman树及其应用

6.1 等长编码

6.2 不等长编码

6.2.1 前缀编码

6.2.2 Huffman编码树与前缀编码

6.3 Huffman编码树建立过程

6.4 Huffman树解码过程

参考资料


 

1. 二叉树(binary tree)概念

1.1 二叉树定义

二叉树由结点的有限集合构成,该集合可以为空集,也可以是由一个根节点(root)和两棵互不相交、分别称作根的左子树(left subtree)和右子树(right subtree)组成。

1.2 二叉树的五种形态

空左、空右、左右都空、独根(左右都不空)、整个都空(连根都没有)。

1.3 二叉树相关术语

(1)边:两个结点的有序对,称作边。有序代表有方向,事实上二叉树是从父结点发向子结点的,是有方向的,但是一般可以省略不写。如下图所示。从A到B的这个过程就 是一个边。

Coursera北大《数据结构基础》之二叉树_第1张图片

(2)层数:根为第0层,其它结点每下降一次为一层。

(3)深度:层数最大的叶结点的层数。

(4)高度:层数最大的叶结点的层数加1。

在上图的例子中,深度是2,高度是3。

1.4 几种特殊的二叉树

1.4.1 满二叉树

如果一棵二叉树的任何结点或是树叶恰好都有两棵非空结点,那么它就是一个满二叉树。

1.4.2 完全二叉树

最多只有最下面的两层结点度数可以小于2,最下一层的结点都集中在最左边。

1.4.3 扩充二叉树

所有空子树都增加空树叶,使得外部路径总长度E和内部路径长度I满足:E=I+2n, n为内部结点个数。

Coursera北大《数据结构基础》之二叉树_第2张图片

1.4.4 表达式二叉树

通过二叉树的不同遍历(会在2.1 讲到遍历方式)来输出不同的表达式。

Coursera北大《数据结构基础》之二叉树_第3张图片

——摘自课程

1.5 二叉树主要性质

  1. 二叉树第i层上最多有2^i个结点(i>=0)
  2. 深度为k的二叉树至多油w^(k+1)-1个结点(k>=0)
  3. 二叉树的终端结点(度为0的结点,即没有子树的结点)数n0与度为2的结点数(即有两个结点)为n2有关系:n0=n2+1 。如下图所示,n0=3, n2=2, 满足n0=n2+1。

    Coursera北大《数据结构基础》之二叉树_第4张图片

     

  4. 【满二叉树定理】非空满二叉树树叶数目等于其分支节点数加1. 例如,如下图所示,树叶(度为0的结点)数目为4,分支结点(度不为0的结点)数目为3,所以4=3+1

    Coursera北大《数据结构基础》之二叉树_第5张图片

     

  5. 【满二叉树定理推论】一个非空二叉树的空子树数目等于其结点数加1.(接上图例子,4=3+1)
  6. 有n个结点的完全二叉树的高度为log_{2}^{n+1},深度为log_{2}^{n+1}-1

2. 二叉树的抽象数据类型

抽象数据类型:逻辑结构+运算

2.1 深度优先搜索(DFS)

顾名思义深搜是按照二叉树深度遍历的。

二叉树深搜栈的深度和树的高度有关,最好是O(log n),最坏为O(n)。

以下三种遍历访问方法的根本区别是根结点在什么时候被访问。在三种遍历中,每个结点都只被访问过一次,因此时间代价为O(n)。

2.1.1 前序遍历(preorder traversal)

tLR次序

  1. 访问根结点
  2. 按前序遍历左子树
  3. 按前序遍历右子树

2.1.2 中序遍历(inorder traversal)

LtR次序

  1. 按中序遍历左子树
  2. 访问根结点
  3. 按中序遍历右子树

2.1.3 后序遍历(postorder traversal)

LRt次序

  1. 按后序遍历左子树
  2. 按后序遍历右子树
  3. 访问根结点

2.1.4 例子

Coursera北大《数据结构基础》之二叉树_第6张图片

前序序列为子左右:ABDGCFHI

中序序列为左子右:DBGEAHFCI

后序序列为左右子:DGEBHIFCA【第一遍我写错了,对我来说刚开始不是很好理解记忆】

2.2 宽度优先搜索(BFS)

从根开始自上而下、从左到右访问,很好理解。还是拿上图举例,宽搜对应的序列为:ABCDEFGHI,FIFO,可以采用队列来存储(深搜可能就需要栈来实现非递归算法,不过最好是使用递归的深搜算法)。

宽搜对应的时间代价为O(n),每个结点入队出队各一次。

宽搜的空间代价与树的虽大宽度有关,最好O(1)(整个树退化为一个单链结构,这样不利于时间代价,所以最合理应该是符合O(logn)的一个树结构),最坏是O(n)(这是一个近似满二叉树的形态,是比较利于时间代价的)。

3. 二叉树的存储结构

3.1 一般存储结构

二叉树不太适合顺序结构存储,所以一般用链式结构(一般用二叉链表)来存储。现在也有很多人用三叉链表来实现。二叉链表和三叉链表结构如下图所示。

Coursera北大《数据结构基础》之二叉树_第7张图片

——摘自课程

下图为一个二叉树的对应二叉链表和三叉链表结构的例子。三叉链表多了红色的从结点指向父母的指针,方便用户更快寻找父结点甚至祖先结点。

Coursera北大《数据结构基础》之二叉树_第8张图片

3.2 完全二叉树存储结构

对于完全二叉树,可以使用线性顺序存储结构(但是注意在逻辑结构上它依然是二叉树形式)。即以宽搜的思想去存储完全二叉树。

对于一个结点数为n的完全二叉树,对于结点i,当2i+1

4. 二叉搜索树(Binary Search Tree, BST)

4.1 定义

二叉树具有下列性质:

对于任何一个结点,若其值为K,则该结点的左子树任意一个结点都小于K,该结点的右子树的任意一个结点的值都大于K;其左右子树也分别为BST。

是一棵空树。

则该二叉树为二叉搜索树。

4.2 二叉搜索树性质

BST具有良好的检索性能,中序遍历是正序(值从大到小排列)。同时为了保护这一性质,在进行插入时要先进行检索。

5. 堆与优先队列

5.1 堆的定义(以最小堆为例)

以最小堆为例,它是一个关键码序列{K0, K1, ..., Kn-1},若其具有以下性质则为最小堆:

K_{i}\leq K_{2i+1}, (i=0, 1, ..., \lfloor{n/2}\rfloor-1)\\ K_{i}\leq K_{2i+2}

排下来其实就是对每一个(子)根节点,它的孩子一定大于它。

类似地,我们可以推出最大堆。

堆(英语:Heap)是计算机科学中的一种特别的树状数据结构。

若是满足以下特性,即可称为堆:“给定堆中任意节点 P 和 C,若 P 是 C 的母节点,那么 P 的值会小于等于(或大于等于) C 的值”。若母节点的值恒小于等于子节点的值,此堆称为最小堆(英语:min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(英语:max heap)

在堆中最顶端的那一个节点,称作根节点(英语:root node),根节点本身没有母节点(英语:parent node)

堆始于 J._W._J._Williams 在 1964 年发表的堆排序(英语:heap sort),当时他提出了二叉堆树作为此算法的数据结构。堆在戴克斯特拉算法(英语:Dijkstra's algorithm)中亦为重要的关键。

在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

——摘自维基百科《堆》【1】

5.2 堆的性质

  • 堆可以用完全二叉树的层次序列表示,因此可以用数组存储;
  • 对于兄弟结点之间,堆没有强制约束,都是在父子的约束上;
  • 从逻辑角度上看,堆实际上是一种树型结构

5.3 对最小堆用筛选法siftdown调整

siftdown过程如下图所示,停止规则就是到了最后一层或者子节点都比根节点大。在建堆时,通常跟下图过程不同,需要从下向上来进行调整(从上到下太乱了)。这样的话,有一半的结点在一开始就自成子堆,便于调整。这样建堆产生的时间效率为O(n)。

类似有siftup操作,向上调整。

Coursera北大《数据结构基础》之二叉树_第9张图片

5.4 最小堆结点的插入和删除

插入删除操作的平均时间代价和最差时间代价都是O(logn)(堆有log(n)层深)。

5.4.1 最小堆插入结点

这个很简单,只需要比较子结点和父节点的大小,做siftup,确保其满足最小堆性质即可

5.4.2 最小堆结点删除

这个要麻烦一些。首先我们要用整个堆中最后的元素A来替换要被删除的元素B,然后看A是需要siftup还是siftdown。如下图所示,我们将用一个最小堆来举两个删除的例子。

(1)所示是原本的最小堆。在(2)中,我们要删除结点77,那么,我们用最后一个元素99来替换77。替换后进行检查,发现9,99,51的子树满足最小堆性质,所以结束删除。

(3)中,我们要删除结点6,依然用99来替换,然后进行检查,发现99需要进行siftdown操作,得到(4),完成删除操作。

Coursera北大《数据结构基础》之二叉树_第10张图片

5.5 优先队列

堆可以用于实现优先队列。

优先队列是根据需要释放具有最小(大)值的对象(谁最小/最大/最需要先得到服务),可以通过索引的形式来帮助队列找到需要的值。

6. Huffman树及其应用

6.1 等长编码

在存储数据时,常用到ASCII码或者中文编码这样的编码方法。编码后,我们需要把数据存储在本地或者外部结构以此方便读取。根据老师的课程,外存(移动硬盘也算外存)读取在效率上比本地差100万倍,因此我们需要一种合理的存储和编码方法将常用的数据存储在本地。

一种可行的方法是等长编码,即假设每一个字符的出现频率和所需存储字节都是相等的,那么我们只需要给每一个字符分配一样的空间大小。显然这样的设定是不合理的,会造成浪费或者资源不够的情况,所以我们需要使用不等长编码。

6.2 不等长编码

不等长编码顾名思义,就是假设字符出现的频率和所需空间是不同的。

6.2.1 前缀编码

如下图例子所示,每一个字母的编码都是特定的,且不为另一个字母编码的前缀,这样我们可以通过一串前缀编码得到唯一的字符串。

Coursera北大《数据结构基础》之二叉树_第11张图片

——摘自课件

6.2.2 Huffman编码树与前缀编码

在建立Huffman编码树时,我们会使用前缀编码的概念。

6.3 Huffman编码树建立过程

Huffman树用堆存储使用,适用于字符频率不等且差别较大的情况,zip压缩就是Huffman树和其它算法的结合。在建立Huffman树中,编码长度取决于对应字符使用的频率或者权重。权越大离根节点越近。具体过程如下图GIF所示(使用贪心法)。

——摘自课程

在构建完上述GIF图片中的树之后,我们使用01来对其进行编码。如下图所示,我们可以规定左0右1,也可以根据习惯自行规定。我们将编码存在编码表中以便未来的使用(直接查表)。

Coursera北大《数据结构基础》之二叉树_第12张图片

——摘自课件

对N个字符建立Huffman编码树,需要合并N次结点。

——摘自课程习题

引理:

含有两个以上结点的一课Huffman树,字符使用频率最小的两个字符是兄弟结点,且其深度不必树中其他任何叶结点浅(在最深层)。

6.4 Huffman树解码过程

从左向右开始逐个读取编码,直至确定一个字符。之后再回到根节点,继续上述过程。

参考资料

【1】https://zh.wikipedia.org/wiki/%E5%A0%86%E7%A9%8D

你可能感兴趣的:(data,structure)