二叉树基本功能的实现基本都是递归的思路,因此虽然代码简单,但逻辑相对抽象。
1.二叉树结构的定义
二叉树的节点包括一个值,和指向左右孩子的两个同类型指针
typedef char BTDataType;
//二叉树节点的结构
typedef struct BinaryTreeNode
{
BTDataType val;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
2.二叉树的前、中、后序遍历
前序遍历的顺序是:根、左子树、右子树
中序遍历的顺序是:左子树、根、右子树
后续遍历的顺序时:左子树、右子树、根
而左子树和右子树遍历的时候也得按相应的前中后序的遍历顺序走。
下面我画个图大家可以感受一下二叉树的前中后序遍历:
2.1二叉树的前序遍历
参考代码如下:
//二叉树的前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%c ", root->val);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
以我们上面那颗二叉树为例子,我们来看一下这段代码是如何运行的,建议大家像我这样,第一次学的时候不妨先动手画画,后续知道他是怎么运行的就可以在脑子中走逻辑想简图了。
2.2二叉树的中序遍历
中序遍历无非是先调用左子树再打印根,然后调用右子树,逻辑上与前序一样,不过顺序不同。
参考代码如下:
//二叉树的中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreeInOrder(root->left);
printf("%c ", root->val);
BinaryTreeInOrder(root->right);
}
2.3二叉树的后序遍历
后序遍历也是顺序调换一下,先左右,最后根。
参考代码如下:
//二叉树的后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%c ", root->val);
}
3.二叉树的高度
二叉树的高度就是二叉树的层数
一颗二叉树的层数 = 根节点所在层 + 左右子树层数中较大的个层数,也就是1+max(lefLlevel,rightLevel);
参考代码如下:
int BinaryTreeHigh(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftLevel = BinaryTreeHigh(root->left);
int rightLevel = BinaryTreeHigh(root->right);
return leftLevel > rightLevel ? 1 + leftLevel : 1 + rightLevel;
//或者
//return fmax(BinaryTreeLevelOrder(root->left), BinaryTreeLevelOrder(root->right)) + 1;
}
4.二叉树的层序遍历
二叉树的层序遍历我们是这样个思路:我们先创建一个空队列,然后我们将根节点放入空队列中,此时根节点在队头,我们再利用取队头元素的函数QueueFront()保存队头元素,为得是在我们pop掉队头元素后能找到根节点,然后将根节点的左右孩子一并导入队头,当然左孩子要先导进队列,注意如果孩子为空的不入队列。
按照这个思路,每pop掉一个队头的根,都将这个根的左右孩子从队尾入队列,直到队列变成空队列,也就意味着我们将二叉树中的所有非空元素全都遍历了一遍。
(注意:我们这边的队列及其基本功能的实现需要自己实现)
队列博客链接: 栈和队列基本功能实现
参考代码如下:
//二叉树的层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{https://blog.csdn.net/Wangshijie1015/article/details/146053878?spm=1001.2014.3001.5501
Queue q;
//初始化队列
QueueInit(&q);
//先将根放入队列
if (root)
QueuePush(&q, root);
//不为空的话就继续遍历
while (!QueueEmpty(&q))
{
//队头元素就是我们要pop的元素,先保存下来
//为的是后续能找到该节点,然后将左右孩子入堆
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->val);
//左孩子为空不入队
if (front->left)
QueuePush(&q, front->left);
//右孩子为空不入队
if (front->right)
QueuePush(&q, front->right);
}
//销毁队列
QueueDestory(&q);
}
5.判断二叉树是否是完全二叉树
思路:与层序遍历不同,我们这此pop掉队头元素后,不管它的左右孩子是否是NULL,我们都将他们从队尾入队列,最后结束的条件不再是判空,而是判断队头元素是否是NULL,如果是,我们接下来就判断队列中是否存在非空元素,如果存在,就不是完全二叉树,反之则是完全二叉树。
参考案例:
参考代码如下:
//判断二叉树是否是完全二叉树
int BinarTreeComplete(BTNode* root)
{
//将二叉树中的节点入队列,空也入
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
//如果是完全二叉树,根是空也代表树走到最后一个节点了
if (front == NULL)
{
break;
}
QueuePop(&q);
//空节点也入队列
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
//如果是完全二叉树,此时队列中剩下的应该都是NULL才对,如果有非空元素,说明不是完全二叉树
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
if (front != NULL)
{
QueueDestory(&q);
return 0;
}
QueuePop(&q);
}
QueueDestory(&q);
return 1;
}
6.前序遍历的数组来构建二叉树
前面是我们已经得到了一颗二叉树,然后我们利用递归来求它的前、中、后序遍历结果,现在有一个数组,数组中存放的是我们二叉树前序遍历的结果,我们需要利用这个结果反推构建一颗二叉树。
思路:
首先我们给的这个数族中#表示这个位置是空
因为是前序遍历的数组,所以数组中的顺序也是根、左子树、右子树,所以我们先把第一个元素放入根的值中:root->val = a[(*pi)++];然后将下标++,使数组能找到下一个元素。
然后第二个值就是我们左子树的根节点的值,我们递归调用该函数,然后该函数走到root->val = a[(*pi)++];的时候就把这个值赋予左子树的根节点了,如果数组当前元素是#,代表这个节点应该是空,所以我们会在if那返回空。
参考代码如下:
//通过前序遍历的数组“ABD##E#H##CF##G##”构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->val = a[(*pi)++];
root->left = BinaryTreeCreate(a, pi);
root->right = BinaryTreeCreate(a, pi);
return root;
}
7.二叉树节点个数
二叉树节点个数=根节点+左子树节点个数+右子树节点个数
参考代码如下:
//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
//空树即意味着0个节点
if (root == NULL)
{
return 0;
}
return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
8.二叉树叶子节点个数
二叉树叶子节点个数=左子树叶子节点个数+右子树叶子节点个数
参考代码如下:
//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
//空树即意味着0个叶子节点
if (root == NULL)
{
return 0;
}
//如果这个节点没有左右孩子,那它就是叶子节点
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
9.二叉树第k层节点个数
二叉树第k层节点个数=左子树第k-1层节点个数+右子树第k-1层节点个数。
参考代码如下:
//二叉树第k层节点个数
int BinaryTreeLevelKOrder(BTNode* root, int k)
{
//求一颗树第k层节点的个数就是求其左右子树第k-1层的节点个数
//空树意味着0个节点
if (root == NULL)
{
return 0;
}
//不是空树,求得又是这棵树的第1层,则这个根就是我们要求的节点
if (k == 1)
{
return 1;
}
//左子树k-1层节点个数 + 右子树节点个数就是二叉树第k层节点个数
return BinaryTreeLevelKOrder(root->left, k - 1) + BinaryTreeLevelKOrder(root->right, k - 1);
}
10.二叉树查找值为x的节点
我们先对比根是不是我们要找的节点,不是的话我们往左子树找,左子树找到的话返回该节点,左子树没有则去右子树找,右子树没有则代表没有,返回NULL。
参考代码如下:
//二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
//子树为空了,返回根
if (root == NULL)
{
return NULL;
}
if (root->val == x)
{
return root;
}
BTNode* node1 = BinaryTreeFind(root->left, x);
//node1不是空就代表找到了
if (node1)
return node1;
BTNode* node2 = BinaryTreeFind(root->right, x);
//node2不是空就代表找到了
if (node2)
return node2;
//走到这步说明前面都没找到
return NULL;
}
11.二叉树销毁
二叉树销毁我们也要按照顺序进行销毁,我们销毁的顺序是左右孩子、根,我们按照这个顺序是应为我们先销毁根,我们还能找到左右孩子吗?我们无法通过孩子找到根,但我们可以通过根找到左右孩子。
参考代码如下:
//二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
if (root == NULL)
{
return;
}
BinaryTreeDestory(root->left);
BinaryTreeDestory(root->right);
free(root);
}