文章目录
一、树的定义
树的基本术语
1.结点:树中的一个独立单元,包含一个数据元素及若干指向其子树的分支。
2.结点的度:结点拥有的子树数称为结点的度
3.树的度:树内各结点度的最大值
4.叶子:度为0的结点称为叶子结点或者终端结点
5.双亲和孩子:结点的子树称为该结点的孩子,对应的,该结点称为孩子的双亲
6.兄弟:同一个双亲的孩子之间互称兄弟
7.祖先:从根到该结点所经分支上的所有结点
8.树的深度:树中结点的最大层次称为树的深度(包含根结点)
9.森林:m棵互不相交的树的集合
二、二叉树
二叉树的定义
二叉树是n个结点所构成的集合,它或为空树或为非空树;对于非空树:
1.有且仅有一个称为根的结点
2.除根结点以外的其余部分分为两个互不相交的子集,分别称之为左子树和右子树,且其本身也为二叉树。
二叉树的特点:
1.二叉树每个结点至多只有两颗子树
2.二叉树的子树有左右之分,其次序不能颠倒
特殊的二叉树
1.满二叉树
每一层上的结点数都是最大的结点数,即每一层结点数都为2^(i-1)
满二叉树的特点:
(1)叶子只能出现在最下一层。
(2)非叶子结点的度一定是 2。
(3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
2.完全二叉树
完全二叉树的特点:
(1)叶子结点只能出现在最下两层。
(2)最下层的叶子一定集中在左部连续的位置。
(3)倒数第二层,若有叶子结点,一定都在右部连续位置。
(4)如果结点度为 1,则该结点只有左孩子,即不存在只有右子树的情况。
(5)同样结点的数的二叉树,完全二叉树的深度最小。
(6)对任一结点,若其右分支下子孙的最大层次为L,则其左分支下的子孙的最大层次必为L或者L+1;
二叉树的性质
性质 1:在二叉树的第 i 层上最多有 2^(i-1) 个结点。
性质 2:深度为 k 的二叉树至多有 2^k-1 个结点(k ≥ 1)
性质3:对任何一棵二叉树 T,如果其叶子结点数为 n0,度为 2 的结点数为 n2,则 n0 = n2 + 1
性质 5:具有 n 个结点的完全二叉树的深度为 ⎣ l o g 2 n ⎦ + 1
性质 6:如果对一棵有 n 个结点的完全二叉树的结点进行编号,则对任一结点 i (1 ≤ i ≤ n),有:
(1)如果 i = 1,则结点 i 是二叉树的根,无双亲;如果 i>1,则其双亲结点是 ⎣ i/2 ⎦。
(2)如果 2i>n,则结点 i 无左孩子(结点 i 为叶子结点);否则其左孩子是结点 2i。
(3)如果 2i +1>n,则结点 i 无右孩子;否则其右孩子是结点 2i +1。
来个图好理解
二叉树的存储结构
顺序存储结构
对于满二叉树和完全二叉树来说,可以将其数据元素逐层存放到一组连续的存储单元中,如图所示,用一维数组来实现顺序存储结构时,将二叉树中编号为 i 的结点存放到数组中的第 i 个分量中。如此根据性质 6,可以得到结点 i 的双亲结点、左右孩子结点分别存放在 i/2 、2i 以及 2i +1 分量中。
这种存储方式对于满二叉树和完全二叉树是非常合适也是高效方便的。因为满二叉树和完全二叉树采用顺序存储结构既不浪费空间,也可以根据公式很快的确定结点之间的关系。但是对于一般的二叉树而言,必须用“虚结点”将一棵二叉树补成一棵完全二叉树来存储,否则无法确定结点之间的关系,但是这样一来就会造成空间的浪费。一种极端的情况是,为了存储 k个结点,需要 2^k-1 个存储单元,如下图所示。此时存储空间浪费巨大,这是顺序存储结构的一个缺点。
链式存储结构
对于一般的二叉树,更适合用链式存储。
对于每个结点,其有一个数据域和两个指针域,用于存储数据信息,和指向其左右孩子的指针
三 、二叉树的遍历
二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
结点定义
typedef struct TreeNode {
int data;
struct TreeNode* lchild, * rchild;
}TreeNode;
前序遍历
若二叉树为空,则空操作;否则
① 访问根结点;
② 前序遍历左子树;
③ 前序遍历右子树。
上图遍历顺序为:ABDGHCEIF。
递归写法
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
非递归写法(借助栈)
前序遍历是中左右,每次先处理的是中间节点,那么先将跟节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢?因为这样出栈的时候才是中左右的顺序。
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
st.push(root);
while<