目录
树的概念及结构
概念:树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
结构看下图
从图中可以看出特点:1、子树是不相交的
2、除了根结点外,每个结点有且仅有一个父结点
3、一棵N个结点的树有N-1条边
结点的度:一个结点含有的子树的个数称为该结点的度,比如上图的A的度为2.
叶结点:度为0的结点
二叉树的概念及结构
概念:一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树的二叉树组成。
二叉树的特点:1、每个结点最多有两棵子树,即二叉树不存在度大于2的结点
2、二叉树的子树有左右之分,其子树的次序不能颠倒
二叉树的结构看下图
特殊的二叉树:满二叉树、完全二叉树
1、满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
2、完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
二叉树的存储结构
二叉树一般可以使用两种结构存储,一种是顺序存储,一种是链式存储
顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树 会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。
二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
根据数组的下标计算该结点对应的父结点和孩子结点
计算该结点的孩子结点位置 (parent 、child 为数组下标)
左孩子:Lchild = parent * 2 + 1; (parent 为当前结点的下标)
右孩子:Rchild = parent * 2 + 2; (parent 为当前结点的下标)
计算该结点的父结点位置 (parent 、child 为数组下标)
当前结点为左孩子(下标为奇数):parent = (Lchild - 1) / 2 ;
当前结点为右孩子(下标为偶数):parent = (Rchild - 2) / 2 ;
在非完全二叉树中,你可能会认为将数组按顺序填满不就不会造成空间浪费了吗?
如果这样做的话,结点的父子关系就错乱了,按照上面的公式就计算不了正确的父子结点
链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的 方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩 子和右孩子所在的链结点的存储地址 。
我们通常是使用链式存储来实现二叉树
结点的代码定义
typedef char BTNodeDataType;
typedef struct BinaryTreeNode
{
BinaryTreeNode* left; //左孩子
BinaryTreeNode* right; //右孩子
BTNodeDataType data; //数值
}BTNode;
二叉树的链式结构遍历
我们通常使用递归方法实现遍历
遍历顺序有三种:前序遍历、中序遍历、后序遍历
前序遍历:根->左孩子->右孩子
中序遍历:左孩子->根->右孩子
后序遍历:左孩子->右孩子->根
前序遍历 根->左孩子->右孩子
递归方法
//前序 根->左孩子->右孩子
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
cout << "NULL ";
return;
}
cout << root->data << " ";
PrevOrder(root->left);
PrevOrder(root->right);
}
非递归方法 借助栈来实现
//非递归方法
void PrevOrder_stack(BTNode* root)
{
stack<BTNode*>st;
if (root != NULL)
st.push(root);
while (!st.empty())
{
BTNode* top = st.top();
st.pop();
if (top == NULL)
{
cout << "NULL ";
continue;
}
cout << top->data << " ";
//因为是前序遍历:根->左孩子->右孩子 而且栈是后进先出
//所以先让右子树进栈,左孩子后进栈
st.push(top->right);
st.push(top->left);
}
}
中序遍历 左孩子->根->右孩子
//中序 左孩子->根->右孩子
void InOrder(BTNode* root)
{
if (root == NULL)
{
cout << "NULL ";
return;
}
InOrder(root->left);
cout << root->data << " ";
InOrder(root->right);
}
后序遍历 左孩子->右孩子->根
//后序 左孩子->右孩子->根
void PostOrder(BTNode* root)
{
if (root == NULL)
{
cout << "NULL ";
return;
}
PostOrder(root->left);
PostOrder(root->right);
cout << root->data << " ";
}
层序遍历
除了前中后序遍历之外,还有一种常用的层序遍历
层序遍历: 非递归 利用队列特点:先进先出 核心思路:上一层带下一层
遍历顺序:设二叉树的 根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然 后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问 树的结点的过程就是层序遍历。
//层序遍历 非递归 利用队列特点:先进先出 核心思路:上一层带下一层
void LevelOrder(BTNode* root)
{
queue<BTNode*>q;
if (root == NULL)
return;
q.push(root);
while (!q.empty())
{
BTNode* front = q.front();
q.pop();
cout << front->data << " ";
if (front->left)
q.push(front->left);
if (front->right)
q.push(front->right);
}
}
特殊的二叉树
除了完全二叉树、满二叉树,我们还有二叉搜索树、平衡二叉树
二叉搜索树:1、左孩子的数值小于其父结点的数值
2、右孩子的数值大于其父结点的数值
特别注意的是二叉搜索树的中序遍历等价于一个升序的数组遍历
平衡二叉树:任意结点的子树的高度差都小于等于 1
题目推荐巩固
1、二叉树的前序遍历力扣https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
2、二叉树的中序遍历力扣https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
3、二叉树的后序遍历力扣https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
4、二叉树的层序遍历力扣https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
5、二叉树的最大深度力扣https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
6、平衡二叉树