3、树和二叉树
1、线性结构是数据结构的较为简单的结构(顺序表,链表,栈,队列)。复杂结构中最简单的一类-----树形结构。
2、树形结构也是由结点和结点之间的连接关系构成,但其结构与线性结构(表)不同,最重要的特征包括:
3、树形结构中最简单的,用得最多的结构----二叉树结
4、二叉树定义
二叉树是结点的有穷集合。这个集合或者是空集,或者其中有
一个称为根结点的特殊结点,其余结点分属两棵不相交的二叉树,这两棵二叉树分别是原二叉树的左子树和右子树。值得注意地是,这个定义是一个递归定义,所定义的二叉树是一种递归结构。一棵二叉树可能有两棵子树,其子树也是二叉树,结构与整棵树相同。对于非空二叉树,其结点集合非空,至少包含一个根结点。但是其子树可以为空,两棵子树都可以为空。如果根结点的子树都空,就是一棵只包含根结点的二叉树。讨论子树时必须明确说明是左子树还是右子树。
5、二叉树的两个特点
1)树中每个结点至多关联到两个后继结点,也就是说一个结点的关联结点的关联结点数可以为0、1或2
2)一个结点关联的后继结点明确地分左右,或为其左关联结点,或为其右关联结点
6、二叉树的基本概念
1)不包含任何结点的二叉树称为—【空树】
2)只包含一个结点的二叉树是----【单点树】
3)一棵二叉树可以包含任意但有穷多的结点
4)一棵二叉树的根结点称为该树的子树根结点的【父节点】;与之对应,子树的根结点称为二叉树树根结点的【子节点】。注意,父节点和子节点的概念是相对的。
5)可以认为从父节点到子节点有一条连线,称为从父节点到子节点的边。注意,这种边有方向,形成一种单方向的父节点/子节点关系(父子关系)。基于父子关系可以定义其传递关系,称为祖先/子孙关系,它决定了一个结点的祖先结点,或子孙结点。父节点相同的两个结点互为【兄弟结点】。所以,一棵二叉树(或其中子树)的根结点r是这棵树中所有其他结点的【祖先结点】,而这些结点都是r的【子孙结点】。
6)二叉树里有些结点的两棵子树都为空,没有子节点。这种结点称为-------【树叶结点】;其余结点称为【分支结点】,分支结点可以只有一个分支(一个子节点)。对于二叉树,只有一个分支时必须说明它是其左分支还是右分支。
7)一个结点的子结点个数称为该结点的【度数】,叶子结点的度数为0,分支结点的度数为1或2。
7、一棵二叉树只有五种可能的形态,如下图,从左到右依次为:空树、单点树、根结点+左子树、根结点+有子树、左右子树俱全。
8、树的路径、路径的长度、结点的层、树的高度(深度)
1)路径:从二叉树或其子树的一个祖先结点到其任何子孙结点都存在一系列边,形成从前者到后者的联系。这样一系列首位相连的边称为树的一条路径。显然,路径唯一。
2)路径长度:一条路径的边数称为该路径的长度
3)结点的层:规定二叉树根的层数为0。对于k层的结点,其子结点是k+1层的元素,如此下去,二叉树的所有结点可以按这种关系分为一层层元素。易见,从树根(非子树!)到树中任一结点的路径长度就是该结点所在的层数,称为该结点的层数。
4)树的高度:树种结点的最大层数,也就是这棵树里的最长路径长度。树的高度是二叉树的整体性质。
3、二叉树的性质。
1)二叉树最重要的性质就是树的高度和树种可以容纳的最大结点个数之间的关系。树的高度类似于表长,是从根结点(首结点)到其他结点的最大距离。在长为n的表里只能容纳n个结点,而在高为h的二叉树中最大可以容纳约2^h个结点。以下为几个重要的性质
2)在非空二叉树第i层中至多有2^i个结点。
3)高度为h的二叉树至多有2^(h+1)-1个结点(h>=0)
4)对于任何非空二叉树T,如果其叶子结点个数为n0,度数为2的结点个数为n2,那么n0 = n2+ 1
3.1、特殊的二叉树(满二叉树/扩充二叉树/完全二叉树)
1、满二叉树
如果二叉树中所有分支结点的度数都是2,则称它为满二叉树。满二叉树一般为二叉树的一个子集。下图为2棵满二叉树实例
2、扩充二叉树
对二叉树T,加入足够多的新叶子结点,使T的原有结点都变成度数为2的分支结点,得到的二叉树称为T的扩充二叉树。扩充二叉树中新增的结点称为其外部结点,原T的结点称为内部结点。下图为一个二叉树变扩充二叉树的过程,可以看出,任何扩充二叉树都是满二叉树。
3、扩充二叉树的性质
扩充二叉树的外部路径长度E是从树根到树中各外部结点的路径长度之和,内部路径长度I是从树根到树中各内部结点的路径长度之和。如果该树有n个内部结点,那么E = I + 2 * n
4、完全二叉树
对于一棵高度为h的二叉树,如果其第0层至第h-1层的结点都满(即,0<=i<=h-1,第i层有2^i个结点)。如果最下一层的结点不满,则所有结点在最左边连续排列,空位都在右边。这样的二叉树就是一棵完全二叉树。
5、完全二叉树性质
3.2、二叉树的ADT及遍历方法
1、二叉树的ADT
2、二叉树的结构比较复杂,有多种的遍历方法。以根结点为起点,有以下2种基本方式:
1)深度优先遍历,顺着一条路径尽可能向前搜索,必要时回溯。对于二叉树,最基本的回溯情况是检查完一个叶结点,由于无路可走,只能回头。是一种递归搜索。
2)宽度优先遍历,在所有路径上一起进行搜索。是一种非递归搜索。
3、深度优先遍历
1)深度优先方式遍历二叉树,需要做三件事:遍历左子树,遍历右子树和访问根结点。
2)按照事情顺序不同,可分为三种遍历方式:左中右,左右中,中左右。
3)由于二叉树的子树也是二叉树,将一种具体的遍历顺序(方法)继续运用到子树的遍历中,就行成了一种遍历二叉树的统一方法。
4)在遍历过程中遇到子树为空的情况,就立即结束处理并转去继续做下一步工作。
例如:分别以左中右、左右中,中左右
左中右:D-H-B-E-I-A-J-F-K-C-G
左右中:H-D-I-E-B-J-K-F-G-C-A
中左右:A-B-D-H-E-I-C-F-J-K-G
4、宽度优先遍历
宽度优先遍历也称按层次顺序遍历,是根据路径长度由近到远地访问结点。这种遍历方法,也就是按二叉树的层次逐层访问树中各结点。与状态空间搜索的情况一样,这种遍历不能写成一个递归过程。在实现这种遍历的时候,需要一个队列作为缓存。上图中的遍历结果为:ABCDEFGHIJK(每一层从左到右)。
5、二叉树的遍历与搜索
1)一次二叉树遍历就是一次覆盖整个状态空间的搜索
2)二叉树的特点:一个结点最多有两个子结点;从一条路走下去,绝不会与另一条路相交
3)遍历是一种系统化的结点枚举过程,实际中未必需要检查全部结点,有时需要在找到所需信息后结束。
4)在状态空间的搜索过程中记录从一个状态到另一个状态的联系,将其看作结点间链接,那么这种搜索过程实际上是一棵搜索树(一般的树,非二叉树)。
3.3、二叉树的list实现
1、二叉树结点就是一个三元组,元素是左右子树和本结点的数据。Python中的list和tuple都可以用于组合这样的三个元素,区别仅在于tuple结构不可更改。
2、二叉树是递归结构,而list也是递归结构,所以很容易用list实现二叉树。例如,用l = [d,l,r]表示一个结点,d代表本结点数据,l为左子树,r为右子树。给出2个list构造二叉树的例子:
代码从水平向(横向)看去,很形象的展现了二叉树的结构。
3.4、二叉树的应用之一~表达式树
1、数学表达式具有分层次的递归结构,一个运算符作用于相应运算对象,其运算对象又可以是任意复杂的表达式。二叉树的递归结构正好用来表示这种表达式,二叉树中的结点与子树的关系可用于表示运算符对运算对象作用的关系。
2、下面考虑的是二元表达式的基本情况,其基本表达式都是数。
1)以基本运算对象(数和变量)作为叶结点中的数据。
2)以运算符作为分支结点的数据,即两棵子树是它的运算对象,子树可以是基本运算对象,也可以是任意复杂的二元表达式。
对该二叉树分别进行左中右、左右中、中左右遍历,结果为:
左中右:a-bxc/d+e 左右中:ab-cd/e+x 中左右:x-ab+/cde
3、由于建立起来的数学表达式绝不会变化,数学运算和操作都是基于已有表达式构造新表达式。因此,用tuple作为实现基础,用三元的tuple实现二叉树结点。例如实现3*(2+5)时,由于基本运算对象放在叶子结点,运算符放在分支结点,所以有如下的代码,显然这是一个左中右遍历
由于会出现大量没有意义的None,所以将其简化为带括号的前缀表达式,括号表示运算符的作用范围。这样的话,表达式由两种结构组成:
1) 如果是序对(tuple),就是运算符作用于运算对象的符合表达式
2) 否则就是基本表达式,也就是单个数/变量之间的运算
4、基于上面的讨论,定义以下几个表达式构造函数:
5、定义表达式处理函数的时候,经常需要区分基本表达式(直接处理)和复合表达式(递归处理)。为此,定义一个判别表达式是基本表达式or复合表达式的函数:
6、表达式求值
7、一个例子
3.5、List二叉树的优先队列实现
1、优先队列是一种队列,只不过是用二叉树实现的话会比较高效。进队按优先级进队;出队时,优先级最高的先出。即:一般情况下最优先出,只有在优先级相同时,才能保证先进先出(原队列中先进队的,在优先队里要实现先出)。
2、优先队列的特点是存入其中的每项数据都有另外一个数值,该数值表示这个项的优先程度,称其为优先级。
3、在访问和弹出的时候,总是当时在这个结构里保存的所有元素中优先级最高的;如果该元素不弹出,再次访问将还得到它。如果同一优先级有多个元素,那么会弹出其中一个,具体是哪一个由内部实现。
4、在优先级相同时,如果要求保证优先级相同的元素先进先出(即队列的FIFO性质),那么要做出效率较低的实现;如果不要求一定是其中最早进入优先队列的元素,那么就存在效率更高的实现。
5、优先队列的操作
1)创建,判断空(清空内容,确定元素个数)
2)插入元素,访问,删除(当时最优先的)
6、基于List的实现
在加入新数据时,设法确定正确的插入位置(用list的insert插入),保证表元素始终按优先顺序列。另一方面,由于考虑到用List实现,为保证访问和弹出最优先数据项的操作能在O(1)时间内完成,最优先的项该出现在表的尾端。
3.6、链表实现二叉树类
1、用一个数据单元表示一个二叉树结点,通过子结点链接(指针)建立结点之间的联系。采用这种表示方法,只要掌握了一棵二叉树的根结点,也就掌握了整个二叉树。
2、二叉树的结点类,类中初始化参数有3个,分别为数据和左右子树,当左右子树默认时,创建出的是叶子结点
Eg:构造一棵包含3个结点的二叉树,变量t作为根结点引用。
3、统计根结点个数函数以及求所有结点的保存的数值之和
其递归处理结构均可用下图表示。
4、二叉树的遍历算法实现(递归实现型)
1)深度优先遍历之根左右:打印各结点数据
2)深度优先遍历之左根右:
3)深度优先遍历之左右根:
深度优先遍历的递归结构均可以用下图来表示
4)宽度优先遍历
略
3.7、二叉树类