文章主要介绍了二叉树,包含其构造与一些简单应用,文章代码使用c语言实现,对初学者友好。如有错误,还望各位不吝赐教。
一、基本概念
1、定义
考虑到平台都是大佬,此处就简单介绍下。😀😀
a、结点
- 根节点、分支节点与叶子节点;
- 双亲节点与孩子节点;
b、度
一个节点包含子树的数量,也就是它的孩子节点数目,就是它的度;
- 简单应用: 一棵树中,所有节点度的和+1,即为节点的数量。
- 例题:在一颗度为3的树中,度为3的节点有2个,度为2的节点有1个,度为1的节点有2个,则叶子节点有多少个? 叶子节点数: 2×3+1×2+2×1+1 -(2+1+2) = 6
c、深度(高度)
- 固定节点数有: 最大深度=节点数,即每一层一个节点; 最小深度$n=\log_{q}{\left ( S \times \left ( q-1 \right )+1 \right ) }$,其中S为总节点数,q为深度。其实就是等比求和公式的变体:$S=\frac{a_{1} \left ( 1-q^{n} \right ) }{1-q}$。在其中,由于根节点只有1个,所以$a_{1} =1$。
- 例题:一颗拥有1000个节点的树度为4,则它的最小深度是? 5.7>5,所以最小深度为6。
二、二叉树
每个节点的度最多为2,每个节点都有且只有左右子树(左右子树可能为空)。
1、常用性质
- 一棵二叉树中,叶子节点的数目等于度为2的节点数目+1,即$n_{0} =n_{2} +1$。 证明: 二叉树中只有度为0、1、2的结点,故:$S=n_{2} +n_{1}+n_{0}$; 又,二叉树中除根节点外,每个节点都有枝干,故:$S=2 \times n_{2}+n_{1}+1$;所以有:$n_{2} +n_{1}+n_{0} = 2 \times n_{2}+n_{1}+1$ 即$n_{0} = n_{2}+1$。
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是$2^{h} -1$; 证明:每一层结点数最多为$2^{h-1}$, 有等比数列求和公式即得;
- 规定根节点的层数为1,具有n个结点的满二叉树的深度为$h=\left \lceil log_{2} \left ( n+1 \right ) \right \rceil$(向上取整); 证明:n个节点,则有 $2^{h-1}-1< n\le 2^{h}-1$; 故 $h-1<log_{2} \left ( n+1 \right ) \le h$ ,从而得证。
2、构造
数组中元素按前序排列,其中以‘#’号代表空,以此数组构造二叉树。 例如:ABD##E#H##CF##G##,此时树的形状如下。
构造难点
- 参数选择: 选择元素时,一个元素使用1次,因此,下标i应当随着调用而逐渐变化,故i应传址调用;
- 递归顺序: 元素按前序排列,因此同样按前序构造二叉树;
代码
// 申请节点
BTNode* BuyNode(BTDataType x)
{
BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
if (!tmp)
{
perror("malloc fail");
exit(-1);
}
// 修改值
tmp->data = x;
tmp->left = tmp->right = NULL;
return tmp;
}
BTNode* TreeCreate(BTNode* root, BTDataType* a, int* pi, int n)
{
// #号返回NULL
if (a[(*pi)] == '#')
{
(*pi)++;
return NULL;
}
root = BuyNode(a[(* pi)++]);
root->left = TreeCreate(root->left, a, pi, n);
root->right = TreeCreate(root->right, a, pi, n);
return root;
}
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
// 创建根节点,随后构造二叉树
BTNode* ret = NULL;
ret = TreeCreate(ret, a, pi, n);
return ret;
}
三、简单应用
在解决树的相关问题时,常常需要考虑分解问题的做法。借由二叉树的逻辑,一般考虑三个小问题:
- 终止条件
- 问题分解
- 特例 除了以上几个基本问题,根据不同的需求,有时需要考虑不同的的特例。
1、简单版
a、求二叉树的深度
- 终止条件:当节点为空时,返回0;
- 问题分解:树的高度等于最深节点的层数,也就是说,可以分别求左右子树高度的较大值,+1即为树的总高度。
代码如下:
int maxDepth(struct TreeNode* root) {
//终止条件
if(root == NULL)
{
return 0;
}
// 问题分解
return fmax(maxDepth(root->left), maxDepth(root->right)) + 1;
}
b、二叉树节点个数
节点为空时返回0,节点不为空时,返回左树节点个数+右树节点个数+1,递归求解;
- 终止条件:空节点;
- 问题分解:左树数量+右树数量+1;
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
c、二叉树叶子节点个数
思路同上,但增加判断条件,节点为空时返回0;节点的左右子树都为空时,表明此时为叶子节点,返回1;同样,叶子节点总数等于左子树中叶子的数量+右树中叶子的数量;
- 终止条件:1、节点为空;2、节点为叶子节点;
- 问题分解:左树叶子数量+右树叶子数量;
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (!root->left && !root->right)
return 1;
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
d、二叉树第k层节点数量
改变判断条件,当到达第k层且节点不为空时,也就是k=1
时,返回1;
- 终止条件:1、空结点;2、到达第k层,结点不为空;
- 问题分解:左树k层结点数量+右树k层结点数量;
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (k == 1 && root)
{
return 1;
}
if (root == NULL)
{
return 0;
}
return BinaryTreeLevelKSize(root->left, k - 1)
+ BinaryTreeLevelKSize(root->right, k - 1);
}
e、二叉树中查找值为x的节点
先找根节点,随后找左右子树,若找到,则返回对应节点的地址,未找到则继续找,遇到空返回;
- 终止条件:找到节点;空节点;
- 问题分解:先找自身,随后找左右子树;
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret = BinaryTreeFind(root->left, x);
// 在左边找到了
if (ret)
return ret;
ret = BinaryTreeFind(root->right, x);
// 在右边找到或没找到都可直接返回
return ret;
}
f、普通遍历(前、中、后)
根据遍历的不同修改访问节点语句的位置,从而达到不同遍历的效果;
- 终止条件:空节点;
- 问题分解:访问左右子树的不同顺序;
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
return;
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
return;
BinaryTreePrevOrder(root->left);
printf("%c ", root->data);
BinaryTreePrevOrder(root->right);
}
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
return;
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
printf("%c ", root->data);
}
g、层序遍历
层序遍历的实现需要借助队列,当队列为空时,将树中节点入队,不为空时出队,同时将其左右孩子入队。
void LevelOrder(Queue* q, BTNode* root)
{
// 访问到空节点之后返回
if (root == NULL)
return;
QueuePush(q, root);
// 否则出队
while (!QueueEmpty(q))
{
BTNode* r = QueueFront(q);
QueuePop(q);
printf("%c ", r->data);
if (r->left)
QueuePush(q, r->left);
if (r->right)
QueuePush(q, r->right);
}
}
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
LevelOrder(&q, root);
QueueDestroy(&q);
}
h、判断二叉树是否为完全二叉树
采用与层序遍历相同的原理,先将树中的节点依次入队,当发现队头为空时,停止入队;此时,若队列中还有非空数据,则表明二叉树不是完全二叉树;若二叉树为完全二叉树,则空节点必定是队列中最后一个节点;
int BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
// 根入队
if (root == NULL)
{
return true;
}
QueuePush(&q, root);
// 将树中节点依次入队,遇到空则停止入队
while (!QueueEmpty(&q))
{
BTNode* f = QueueFront(&q);
if (f == NULL)
{
break;
}
QueuePop(&q);
if(root->left)
QueuePush(&q, f->left);
if(root->right)
QueuePush(&q, f->right);
}
// 判断剩余的元素中是否非空元素
while (!QueueEmpty(&q))
{
BTNode* f = QueueFront(&q);
QueuePop(&q);
// 存在非空节点
if (f != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}