前言
之前在博客【数据结构】树的基本认识【数据结构】树的基本认识_树的基本概念 数据结构-CSDN博客中已经介绍了树的基本的概念,也知道了树的结构其实是多种多样,但其中二叉树是最基础,最常用的一种结构。
一、二叉树的定义
1.定义
二叉树是n(n≥0)个结点的有限集合:
① 或者为空二叉树,即n=0。
② 或者由一个根结点和两个互不相交的被称为根的左子树 和右子树组成。左子树和右子树又分别是一棵二叉树。
2.特点
① 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
② 二叉树的子树有左右之分,其子树的次序不能颠倒。
二、二叉树结构
1、二叉树的五种基本形态
-
1.空树
-
2.只有一个根结点
-
3.根结点只有左子树
-
4.根结点只有右子树
-
5.根结点既有左子树又有右子树
2、特殊的二叉树
1. 斜树
①左斜树:所有的结点只有左子树
②右斜树:所有的结点只有右子树
特点:每层只有一个结点,结点的个数等于二叉树的深度
2. 满二叉树
定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
特点:①所有的非终端结点都存在左右子树,并且所有的叶子结点都在最下面一层
②非叶子结点度一定为2
③同一深度的二叉树中,满二叉树的结点数最多,叶子结点数最多
3. 完全二叉树
定义:对一棵树具有n个结点的二叉树按层次编号,当且仅当其每一个结点都与同深度的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
特点:①叶子结点只能出现在最下面两层
②最下层的叶子结点一定集中于左部连续位置
③如果结点的度为1,则该结点只有左孩子,即不可能存在只有右孩子的情况
④相同结点数的二叉树中,完全二叉树的深度最小
三、二叉树的性质
-
非空二叉树上叶子结点数等于度为2的结点数加1
-
非空二叉树上第K层上至多有2^k−1个结点(K≥1)
-
高度为H的二叉树至多有2^H-1个结点(H≥1)
-
具有N个(N>0)结点的完全二叉树的高度为 [log2(N+1)]或[log2N] +1。
-
对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1。
一棵二叉树,除了终端结点(叶子结点),就是度为1或2的结点。假设n1度为1的结点数,则数T 的结点总数n=n0+n1+n2。我们再换个角度,看一下树T的连接线数,由于根结点只有分支出去,没有分支进入,所以连接线数为结点总数减去1。也就是n-1=n1+2n2,可推导出n0+n1+n2-1 = n1+2n2,继续推导可得n0 = n2+1。
四、二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1、 顺序存储
-
二叉树的顺序存储结构就是用一组地址连续的存储单元(数组)依次自上而下、自左至右存储完全二叉树上的结点元素。
-
顺序结构存储适合表示完全二叉树,因为不是完全二叉树会有空间的浪费(尤其是斜树)。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
代码实现:
#include <iostream>
#include <vector>
using namespace std;
/* 数组表示下的二叉树类 */
class ArrayBinaryTree {
private:
vector<int> tree;
public:
ArrayBinaryTree(vector<int> arr);
int size(); //获取数组长度(包含None(空)节点)
int val(int i); //获取索引i的值
int left(int i); //获取左子节点索引
int right(int i); //获取右子节点索引
int parent(int i); //获取父节点索引
vector<int> levelOrder(); //层次遍历
void preOrder(int root,vector<int>& res); //前序遍历
void inOrder(int root, vector<int>& res); //中序遍历
void postOrder(int root, vector<int>& res); //后序遍历
};
/* 构造方法 */
ArrayBinaryTree ::ArrayBinaryTree(vector<int> arr):tree(arr)
{
}
int ArrayBinaryTree::size()
{
return tree.size();
}
/* 获取索引为 i 节点的值 */
int ArrayBinaryTree::val(int i)
{
// 若索引越界,则返回 INT_MAX ,代表空位
if (i < 0 || i >= size())
{
return INT_MAX;
}
return tree[i];
}
/* 获取索引为 i 节点的左子节点的索引 */
int ArrayBinaryTree::left(int i)
{
return 2 * i + 1;
}
/* 获取索引为 i 节点的右子节点的索引 */
int ArrayBinaryTree::right(int i)
{
return 2 * i + 2;
}
/* 获取索引为 i 节点的父节点的索引 */
int ArrayBinaryTree::parent(int i)
{
return (i - 1) / 2;
}
/* 层序遍历 */
vector<int> ArrayBinaryTree::levelOrder()
{
vector<int> res;
// 直接遍历数组
for (int i = 0; i < size(); i++) {
if (val(i) != INT_MAX)
res.push_back(val(i));
}
return res;
}
//前序遍历
void ArrayBinaryTree::preOrder(int root, vector<int>& res)
{
if (val(root) == INT_MAX)
{
return;
}
res.push_back(val(root));
preOrder(left(root), res);
preOrder(right(root), res);
}
//中序遍历
void ArrayBinaryTree::inOrder(int root, vector<int>& res)
{
if (val(root) == INT_MAX)
{
return;
}
inOrder(left(root), res);
res.push_back(val(root));
inOrder(right(root), res);
}
//后序遍历
void ArrayBinaryTree::postOrder(int root, vector<int>& res)
{
if (val(root) == INT_MAX)
{
return;
}
postOrder(left(root), res);
postOrder(right(root), res);
res.push_back(val(root));
}
2、链式存储
用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。
链表结点:每个结点包含一个数据域和两个指针域
二叉链和三叉链
二叉链表和三叉链表结点代码如下(示例):
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
DataType _data; // 当前节点数据域
}
// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pParent; // 指向当前节点的双亲
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
DataType _data; // 当前节点数据域
};
具体代码实现可以参考:【数据结构】二叉树的遍历方式_lnr左根右-CSDN博客