持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情
1、写在前面
大家好,我是翼同学。今天文章的内容是:
- 二叉树
2、内容
2.1、简介
(1) 概念
二叉树的定义如下:
二叉树:由 n 的结点组成的有限集合。该集合或者为空,或者由一个根结点以及两颗互不相交的左、右子树构成,而左右子树又都是二叉树。
因此,二叉树是一种重要的树结构。在二叉树中,每个结点都最多有两个子结点。
(2) 特点
- 二叉树可以是空的,也就是说空二叉树不含任何结点;
- 二叉树的每个结点最多有两个子结点(度小于或等于2),分别称为该结点的左孩子和右孩子;
- 二叉树的子树有左右之分,即使只有一个子树,也必须说明是左子树还是右子树(交换一个二叉树的左右子树后得到的是另一颗二叉树);
- 度为2的有序树并不是二叉树。因为在有序树中,删除某个度为2的结点的第一子树后,第二子树就会代替第一子树。但是在二叉树中,如果删除了某结点的左子树,则左子树为空,右子树仍然是右子树。
(3) 分类
满二叉树
如果在一个二叉树中,任意层次的结点个数都达到了最大值,所有分支结点都存在左右子树,并且所有叶结点都在同一层次上,我们将这样的二叉树称为满二叉树。
满二叉树的示意图如下:
满二叉树的特点有:
- 满二叉树只有度为
0的结点和度为2的结点。 - 度为
0的结点(叶结点)只能出现在最底层。 - 在同样深度的二叉树中,满二叉树的结点个数最多,叶结点也最多。
完全二叉树
如果在一个二叉树中,只有最底下的两层结点的度可以小于2,并且最下面的结点都集中在该层的最左边的连续位置上。就称这个二叉树为完全二叉树。
完全二叉树的示意图如下:
完全二叉树的特点:
- 叶结点只会出现在层次最大的两层上;
- 任意一个结点,如果没有左子树,那么一定没有右子树;
- 满二叉树一定是完全二叉树,而完全二叉树不一定是满二叉树。
总结来说,对于深度为k,有n个结点的完全二叉树,除第k层外,其余各层次的结点个数都达到最大,而第k层的所有结点都集中在该层的最左边的连续位置上。
举个两个例子:
其他二叉树
- 正则二叉树:如果一棵树的任意结点不是叶结点就是有两颗非空子树,则将这棵树称为正则二叉树。也就是说,正则二叉树不存在度为1的结点,因此有时正则二叉树也被称为严格二叉树。
- 扩充二叉树:在原先二叉树中出现空子树的位置上,增加空的叶结点所形成的二叉树被我们称为扩充二叉树。
- 二叉搜索树:二叉搜索树的特点是,如果根结点的左子树不空,则左子树上所有结点的值均小于根结点的值;如果它的右子树不空,则右子树上所有结点的值均大于根结点的值;并且,每个结点的左右子树又分别是二叉搜索树。
(4) 性质
- 深度为 的二叉树中最多有 个结点
- 非空二叉树的第 层上最多有 个结点(也就是说,如果第 层已经达到了 个结点,那么该树必定是满二叉树)
- 在任意二叉树中,如果叶结点的个数为 ,度为2的结点个数为 ,则
- 扩充二叉树中新增的外部结点的个数等于原二叉树的结点个数加一
- 具有 个结点的完全二叉树的深度为
另外,如果对一颗有个结点的完全二叉树按层次自上而下(每层从左到右)对结点进行编号(从到),则对任意结点而言:
- 如果结点的编号 等于 ,则结点 为根结点(没有父结点)
- 如果 大于 ,则该结点 的父节点编号为
- 如果 小于或等于 ,则 的左孩子的编号为 ,否则 无左孩子
- 如果 小于或等于 ,则 的右孩子的编号为 ,否则 无右孩子。
(5) 抽象数据类型定义
二叉树的抽象数据类型定义如下:
// 二叉树的抽象数据类型定义
template <class T>
class ADTbinaryTree {
public:
virtual void clear() = 0; // 清空
virtual bool empty() const = 0; // 判空
virtual int height() const = 0; // 高度
virtual int size() const = 0; // 结点总数
virtual void preOrderTraverse() const = 0; // 前序遍历
virtual void inOrderTraverse() const = 0; // 中序遍历
virtual void postOrderTraverse() const = 0; // 后序遍历
virtual void levelOrderTraverse() const = 0; // 层次遍历
virtual ~ADTbinaryTree() { }; // 析构
};
2.2、二叉树实现
(1) 顺序存储结构
对于二叉树而言,如果是完全二叉树,我们可以采用顺序存储结构来实现二叉树。但如果是一般二叉树,通常我们会选择链式存储结构来存储二叉树,为了不造成存储空间上的浪费。
简单介绍下二叉树的顺序存储结构:
二叉树的顺序存储结构就是一组地址连续的存储单元依次从上而下,从左到右地存储二叉树的结点,并且在存储结点时,每个结点的的存储位置(数组下标)可以体现结点之间的逻辑关系。也就是利用到了二叉树的性质。
这段话怎么理解呢?
回顾性质:
如果对一颗有个结点的完全二叉树按层次自上而下(每层从左到右)对结点进行编号(从到),则对任意结点而言:
- 如果结点的编号 等于 ,则结点 为根结点(没有父结点)
- 如果 大于 ,则该结点 的父节点编号为
- 如果 小于或等于 ,则 的左孩子的编号为 ,否则 无左孩子
- 如果 小于或等于 ,则 的右孩子的编号为 ,否则 无右孩子。
画一个完全二叉树如下:
为了体现结点之间的逻辑关系,利用二叉树编号的性质,用顺序存储结构来表示该二叉树的示意图如下:
在上图完全二叉树中有9个结点,即。
此时举个例子:编号为4的结点为,又因为,即,所以结点的左孩子的编号为 (也就是),可以观察到,上图中结点的左孩子确实是。
像这样,从上而下,从左到右地将二叉树的结点存储下来,并利用二叉树性质,就可以轻松地体现二叉树各个结点之间的逻辑关系。这就是二叉树的顺序存储结构。
(2) 链式存储结构
当我们采取链式存储结构来存储二叉树时,每个结点除了存储数据元素本身的值data外,还需要设置两个指针,分别用于指向左、右子树。当任意结点的某个子树为空时,对应的指针就可以设置为NULL。如下所示:
另外,我们会设置一个指针root,用于指向二叉树的根结点。
示意图如下:
因此类定义如下:
template <class T>
class BinaryTree : public ADTbinaryTree<T> {
private:
// 二叉链表的结点
struct Node{
T data; // 结点的数据域
Node *left; // 指向左孩子的指针
Node *right; // 指向右孩子的指针
// 无参构造函数
Node() : left(NULL), right(NULL) { }
// 有参构造函数
Node(const T &val, Node *l=NULL, Node *r=NULL) {
data = val;
left = l;
right = r;
}
~Node() { }
}
Node *root; // 指向根结点的指针 root
void clear( Node *t); // 清空函数
int height( Node *t) const; // 二叉树的高度
int size( Node *t) const; // 二叉树的结点总数
int leafNum( Node *t) const; // 二叉树的叶节点总数
void preOrder( Node *t) const; // 前序遍历
void inOrder( Node *t) const; // 中序遍历
void postOrder( Node *t) const; // 后序遍历
void preOrderCreate(T flag, Node* &t); // 创建二叉树
public:
// 构造空的二叉树
BinaryTree() : root(NULL) {}
// 析构函数
~BinaryTree() { clear(); }
// 判断二叉树是否为空
bool empty() const {
return root == NULL;
}
// 清空函数
void clear() {
if(root) clear(root);
root = NULL;
}
// 求结点的总数
int size() const {
return size(root);
}
// 求二叉树的深度
int heigh() const {
return heigh(root);
}
// 求二叉树的结点总数
int leafNum() const {
return leafNum(root);
}
// 前序遍历(递归)
void preOrderTraverse() const {
if(root) {
preOrder(root);
}
}
// 中序遍历(递归)
void inOrderTraverse() const {
if(root) {
inOrder(root);
}
}
// 后序遍历(递归)
void postOrderTraverse() const {
if(root) {
postOrder(root);
}
}
// 层次遍历
void levelOrderTraverse();
// 前序遍历(非递归)
void preOrderTraverse() const;
// 中序遍历(非递归)
void inOrderTraverse() const;
// 后序遍历(非递归)
void postOrderTraverse() const;
// 利用带外部结点的层次序列来创建二叉树
void levelOrderCreate(T flag);
// 利用带外部结点的前序序列来创建二叉树
void preOrderCreate(T flag) {
preOrderCreate(flag, root);
}
};
3、写在最后
好了,文章的内容就到这里,感谢观看。