目录
- 前言
- 1. 前序遍历
- 2. 中序遍历
- 3. 后续遍历
- 4. 二叉树结点的个数
- 5. 二叉树叶子结点个数
- 6. 二叉树的高度
- 7. 二叉树第K层结点的个数
- 8. 二叉树查找值为x的结点
- 全部代码
- 总结
正文开始
前言
本文旨在介绍二叉树的链式存储中一些函数的实现
博客主页: 酷酷学!!!
更多文章, 期待关注~
前置说明: 在学习二叉树的基本操作前, 需要先创建一棵二叉树, 然后才能学习相关的基本操作. 由于目前阶段对二叉树结构掌握不够深入, 为了降低大家学习成本, 此处手动快速创建一棵简单的二叉树, 快速进入二叉树操作学习, 等二叉树结构了解查差不多时, 我们反过头来再研究二叉树真正的创建方式.
typedef int BTDatatype;typedef struct BTNode
{BTDatatype data;struct BTNode* left;struct BTNode* right;
}BTNode;BTNode* BuyNode(int x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}BTNode* CreateTree()
{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 node1;
}
注意: 上述代码并不是创建二叉树的方式, 真正创建二叉树方式期待后续博客
首先实现下面的方法之前我们先来回顾一下二叉树:
什么是二叉树?
- 空树
- 非空:根结点,根结点的左子树、根结点的右子树组成的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
1. 前序遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
我们先来看前序遍历:
前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。简单来说遍历的顺序为 根 -> 左子树 ->右子树
递归的本质: 拆成当前问题和子问题, 返回条件: 最小规模的子问题
我们可以把大问题化小, 首先拆成根, 左子树, 右子树的结构, 然后左子树 又可以拆成 根 左子树 右子树的结构, 结束条件为树为空树.
代码实现:
void PrevOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}
运行结果:
前序遍历递归图解:
如图我们可以看出:
前序遍历依次访问的数据为:
1 2 3 4 5 6
如果加上对空树的访问, NULL简写为N:
1 2 3 N N N 4 5 N N 6 N N
下面为递归展开图, 不太理解的伙伴可以参考:
2. 中序遍历
中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
即, 每棵树 先访问左子树, 在访问根, 最后访问右子树
void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}
递归展开图:
3. 后续遍历
后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
即, 每一棵树,先访问左子树, 在访问右子树, 最后访问根
void PostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}
4. 二叉树结点的个数
求二叉树结点的个数, 这里初学者很容易先到使用遍历来求结点个数,如下:
// 错误示范
int TreeSize(BTNode* root)
{static int size = 0;if (root == NULL)return 0;else++size;TreeSize(root->left);TreeSize(root->right);return size;
}
首先做个铺垫再来讲解这段代码的问题
静态全局变量和静态局部变量的区别在于作用域和生存周期。
静态全局变量: 静态全局变量在整个文件中都是可见的,即其作用域为整个文件。静态全局变量只能被当前文件内的函数访问,其他文件无法访问。静态全局变量的生存周期为整个程序的执行期间,即在程序启动时分配内存,在程序结束时释放内存。
静态局部变量: 静态局部变量只在定义它的函数内部可见,即其作用域为定义它的函数内部。静态局部变量的生存周期为整个程序的执行期间,即在程序启动时分配内存,在程序结束时释放内存。与普通局部变量不同的是,静态局部变量只会在第一次进入函数时初始化一次,之后每次进入函数都会保留上一次的值。
静态全局变量和静态局部变量都可以被修改,但是有一些区别:
静态全局变量: 静态全局变量可以被当前文件内的任何函数修改,因为其作用域为整个文件。其他文件无法直接修改静态全局变量。但是,由于其作用域广泛,可能会被不同函数多次修改,导致程序的可维护性降低。
静态局部变量: 静态局部变量只能在定义它的函数内部被修改,其他函数无法直接修改静态局部变量。由于其作用域限制在函数内部,静态局部变量对于其他函数来说是不可见的,因此可以更好地控制变量的访问权限。此外,静态局部变量在函数调用之间保持其值不变,可以用于在函数调用之间保持状态或者记录某些信息。
因为每次调用函数size都是在栈区开辟的局部变量, 当函数结束后局部变量也会被系统回收, 这里用了static来修饰size, 看似没什么问题, 但是如果多次调用函数问题就来了
那么如何解决, 我们可以使用全局变量:
int size = 0;
int TreeSize(BTNode* root)
{if (root == NULL)return 0;else++size;TreeSize(root->left);TreeSize(root->right);return size;
}void TreeSize(BTNode* root, int* psize)
{if (root == NULL)return 0;else++(*psize);TreeSize(root->left, psize); TreeSize(root->right, psize);
}
但是上述的写法虽然可以, 但是每次调用之前都需要重置size的值,过于麻烦
int size = 0;
TreeSize(root, &size);
printf("TreeSize:%d\n",size);size = 0;
TreeSize(root, &size);
printf("TreeSize:%d\n", size);
最好的解决方案还是回归本质, 因为二叉树本身就是递归定义的, 我们采用递归的思想, 分而治之, 问题就迎刃而解
int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
首先分为左子树和右子树, 每一棵树都有左子树和右子树, 我们只需要统计每一棵树的左子树和右子树总结点个数加上自己.为空就等于0, 不为空左子树结点个数加上右子树结点个数, 返回给上一层
5. 二叉树叶子结点个数
叶子结点的个数, 空树就返回0, 递归的思想, 如果左子树和右子树都是空, 则就是叶子节点, 返回1, 统计左子树和右子树的叶子节点个数即可.
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
6. 二叉树的高度
还是采用递归的思想, 一棵树的高度等于左子树的高度与右子树的高度较高的那个树加+1, 结束条件空树就是0.
int TreeHeight(BTNode* root)
{if (root == NULL){return 0;}int left = TreeHeight(root->left);int right = TreeHeight(root->right);return left > right ? left + 1 : right + 1;
}
7. 二叉树第K层结点的个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}//子问题return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
将复杂度问题划分为子问题, 每一层即下一层的k-1层结点的个数, 如果k==1则返回1.
8. 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDatatype x)
{if (root == NULL){return NULL;}if (root == x){return root;}BTNode* ret1 = BinaryTreeFind(root->left, x);if (ret1){return ret1;}BTNode* ret2 = BinaryTreeFind(root->right, x);if (ret2){return ret2;}return NULL;}
这里要返回一个结点, 因为是递归调用函数, 所以返回值会逐层返回, 将树划分为子问题, 现在如果为NULL,则返回NULL, 如果找到了就返回该结点, 没找到就调用函数去查找左子树与右子树, 如果左子树找到了就返回左子树的结点不需要去右子树查找了, 如果都没找到则返回NULL.
全部代码
Tree.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int BTDatatype;typedef struct BTNode
{BTDatatype data;struct BTNode* left;struct BTNode* right;
}BTNode;BTNode* BuyNode(int x);void PrevOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root);//二叉树结点的个数
int BinaryTreeSize(BTNode* root);
//二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
//二叉树的高度
int TreeHeight(BTNode* root);//二叉树第K层结点的个数
int BinaryTreeLevelKSize(BTNode* root ,int k);//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDatatype x);
Tree.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"BTNode* BuyNode(int x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}void PrevOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}void PostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}int TreeHeight(BTNode* root)
{if (root == NULL){return 0;}int left = TreeHeight(root->left);int right = TreeHeight(root->right);return left > right ? left + 1 : right + 1;
}int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}BTNode* BinaryTreeFind(BTNode* root, BTDatatype x)
{if (root == NULL){return NULL;}if (root == x){return root;}BTNode* ret1 = BinaryTreeFind(root->left, x);if (ret1){return ret1;}BTNode* ret2 = BinaryTreeFind(root->right, x);if (ret2){return ret2;}return NULL;}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"BTNode* CreateTree()
{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 node1;
}int main()
{BTNode* root = CreateTree();PrevOrder(root);//InOrder(root);//PostOrder(root);//int size = BinaryTreeSize(root);//int size = TreeHeight(root);//int size = BinaryTreeLeafSize(root);//int size = BinaryTreeLevelKSize(root,3);//printf("%d ", size);return 0;
}
总结
关于二叉树这块比较复杂, 递归的思路需要清晰, 每一次递归调用的作用是什么, 为什么要这样写, 写代码之前手动画一遍递归调用简图, 只要思路通了, 代码就顺了, 不要觉得是在浪费时间, 因为时间本就是来解决问题的.
完, 感谢关注~