普通链式二叉树(1)
前面我们已经讲了用数组来表示二叉树的方式和功能。这节课,我们就来讲讲普通的链式二叉树。
啥叫链式二叉树呢,其实很简单,就像链表那样,通过指针标记左右孩子节点。结构如下图:
遍历顺序(方式)
那么对于链式二叉树呢,我们首先要学习的就是它的几种遍历方式:前序遍历,中序遍历,后序遍历,后续还有一个层序遍历。我们先讲一下前面3种。
前序遍历
首先我这里讲的前中后,到底是谁的前中后呢?答案是:二叉树的根节点的前中后序。就以前序遍历来说,前序遍历也可以说是先根遍历。也就是先遍历根节点,然后遍历左子树,最后遍历右子树。这里大家可以发现,我没有说遍历左孩子,右孩子。
那么这里就涉及到了链式二叉树的一个重要思路:递归遍历。在一开始介绍树的时候,我提到了(树的递归定义)
大家通过上图,应该是可以理解树和子树的概念了。那么现在我用一颗简单的链式二叉树使用一次前序遍历给大家看。(因为无法视频演示,希望大家可以看懂)
中序遍历
和前序没什么区别,其实这三种遍历没有本质区别,就是遍历根节点的先后问题。是先遍历根节点还是中间遍历根节点,还是最后遍历根节点。
后序遍历
作用
那么普通链式二叉树有啥作用呢?目前来看是没啥作用哈。你看,之前我们学习顺序表昂,链表,堆这些都要实现增,删,查,改,这4个功能。那么我们学习普通链式二叉树要实现这些吗?不用哈。
首先呢,普通链式二叉树能做到的,链表这些简单数据结构也可以做到,所以普通链式二叉树就没必要多插一脚。而且对于普通链式二叉树来说,插入数据,插入哪里呢?删除数据,删除哪一个呢?这样做又有什么用呢?
所以说,普通的链式二叉树没啥用处。但是,为什么我们还要学呢?因为这是为了后面的高阶数据结构,那种听着就很nb的那种数据结构做铺垫,比如二叉搜索树,AVL树,红黑树等等。其次就是,考试会出题,面试可能也会出题,也就是应付考试。
手工建树
为了更好的学习后面的知识呢,我们得人工的建一颗链式二叉树出来。为什么说人工呢,因为这就是一个一个节点创建出来,然后链接起来。这并不是真正的「创建链式二叉树」,意思就是说这并不是最权威的建树方法。后面我们再着重讲怎么建树比较权威。
#include<stdio.h>
#include<stdlib.h>
#include <assert.h>
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
int val;
}BTNode;
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->val = x;
node->left = NULL;
node->right = NULL;
return node;
}
int main()
{
// 手动构建
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return 0;
}
递归实现顺序遍历
完整测试代码
#include<stdio.h>
#include<stdlib.h>
#include <assert.h>
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
int val;
}BTNode;
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->val = x;
node->left = NULL;
node->right = NULL;
return node;
}
void PrevOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
printf("%d ", root->val);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->val);
}
int main()
{
// 手动构建
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
PrevOrder(node1);
printf(("\n"));
InOrder(node1);
printf("\n");
PostOrder(node1);
return 0;
}
依旧是前中后序遍历,现在我们就要写代码实现前中后序遍历。
- 前序遍历
void PrevOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
printf("%d ", root->val);
PrevOrder(root->left);
PrevOrder(root->right);
}
由于没办法在你身边演示给你看,所以大家结合上面前序遍历的图解,还有自己调试一下代码,看一下代码运行的过程,结合递归的知识,我想应该是可以明白的。
- 中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}
- 后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->val);
}
递归实现分治
后面是会有几个小问题,都需要用到分治的思想解决。
那么分治是啥意思呢?很简单,分开统治。这就像公司各级领导,一级领导统领二级领导,二级领导接着统领三级领导,以此类推。在学校,校长统领各院院长,院长统领系领导,系领导又统领各普通老师/辅导员,老师又统领各班班长,班长负责管理班级学生。这就是分而治之,也就是分治。
那么这样分级的意义是啥呢?这么说吧,假设教育局叫校长统计一下到校人数,难道校长要亲自到每个班级,每个宿舍里,点名,然后点到一个记录一个吗?那当然不会,这样校长哪里还有时间去找小美昂?对吧
很简单,校长叫院长办事,院长叫系主任办事,系主任叫老师办事,老师叫班长办事,所以一个大问题(统计全部到校人数)就变成了一个小问题(计算每个班的人数),最后层层往上上报想加就搞定了。可以参考下图:
了解分治大概思想之后就开始实践一下吧
全部测试代码:
#include<stdio.h>
#include<stdlib.h>
#include <assert.h>
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
int val;
}BTNode;
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->val = x;
node->left = NULL;
node->right = NULL;
return node;
}
void PrevOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
printf("%d ", root->val);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->val);
}
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
// 叶子节点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
// 第k层的节点个数
int TreeKLevel(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
return 0;
if (k == 1)
{
return 1;
}
return TreeKLevel(root->left, k - 1)
+ TreeKLevel(root->right, k - 1);
}
int main()
{
// 手动构建
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
PrevOrder(node1);
printf(("\n"));
InOrder(node1);
printf("\n");
PostOrder(node1);
printf("\n");
printf("%d\n", TreeSize(node1));
return 0;
}
大家调用函数自己去试就好了
- 计算一棵树的节点个数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
- 计算一棵树的叶子节点的个数
// 叶子节点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
- 计算一棵树第k层节点个数
// 第k层的节点个数
int TreeKLevel(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
return 0;
if (k == 1)
{
return 1;
}
return TreeKLevel(root->left, k - 1)
+ TreeKLevel(root->right, k - 1);
}