二叉树的遍历有何意义?
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
二叉树的递归结构遍历
递归就是在运行的过程中调用自己,以实现层次数据结构的查询和访问;结构指的是二叉树是树的一种特殊结构;遍历是指沿着某条搜索路线,依次对树中每个节点做一次且仅做一次访问。
写一个结构体
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//给int起一个BTDateType的别称,及BTDataType就是int
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;//结构体的数据
struct BinaryTreeNode* left;//结构体成员变量
struct BinaryTreeNode* right;//结构体成员变量
}BTNode;//二叉树的别名,代表这个结构体
// 二叉树前序遍历
前序
前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。简单来记忆:根左右
二叉树前序遍历:
void PreOrder(BTNode* root)
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);//访问根节点
PreOrder(root->left);//d=递归左子树
PreOrder(root->right);//递归右子树
}
前序我们把二叉树分成根和左子树、右子树3个部分,每一个节点都可以往下分成这3个部分,遇到空树就停下来,先访问根节点1,节点1访问它的左子树2,节点2再访问根->它自己,依次访问到左子树3,节点3的左子树访问到7,节点7访问到左右2个空,节点3再访问它的右子树节点8,节点8再访问到2个空,这样节点1的左子树访问完了,访问它的右子树节点节点4,节点4的左右子树是节点5,节点5访问到节点2个空停下来,节点4访问右子树节点6,节点6走到2个空,访问结束。
因此前序遍历打印的结果是:
1 2 3 7 NULL NULL 8 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL
中序
中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。简单来记忆:左根右
二叉树中序遍历:
void InOrder(BTNode* root)
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);//访问根节点
InOrder(root->right);
}
节点1走到节点2,节点2走到节点3,节点3走到节点7,节点7走到空,然后回退访问根->自己7,再走到节点7的右子树是个空,然后回退到节点3,节点3的左子树访问完了就该访问它自己了,节点3能不能走到节点8,不能,节点3先走到它的左子树为空再走到节点8,节点8的左子树为空,节点3就走完了回退到节点2,节点2的左子树是空回退到它的根节点1,节点1走到节点4不能直接访问节点5,走到5的左子树访问空,再访问节点5,再访问它的右子树空,节点5回退到4,访问节点5的右树也就是节点4的左子树为空,节点4就可以访问了,能访问6吗不能,先访问4的左子树的左子树空,再去访问节点6,最后访问节点6的右子树。
因此中序遍历打印的结果是:NULL 7 NULL 3 NULL 8 NULL 2 NULL 1NULL 5 NULL 4NUU 6 NULL
后序
后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。简单来记忆:左右根
二叉树后序遍历:
void PostOrder(BTNode* root)
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);//d=递归左子树
PostOrder(root->right);//递归右子树
printf("%d ", root->data);//访问根节点
}
先访问节点7的左右2个空,再访问根->它自己7,节点7作为节点3的左,不能先访问节点3,先访问节点3的右8,节点8又作为根又不能直接访问,先访问节点8的左右2个空才能访问到节点8,节点3的左右访问完了才回退访问到节点3,节点3作为节点2的左又不能直接访问到节点2,得先访问节点2的右是空,然后访问到节点2,节点2作为节点1的左不能直接访问到节点1,先访问节点1的右为2个空,再访问到根,这个根是节点5了而不是节点1,因为访问的是节点5的左右,节点4的左是节点5访问完了再访问它的右节点6不能直接访问,访问节点6的左右2个空再访问节点6,节点4的左右都访问完了才访问它自己节点4,节点4又作为节点1的右树,此时访问到节点1就结束。
因此后序遍历打印的结果是:
NULL NULL 7 NULL NULL 8 3 NULL2 NULL NULL 5 NULL NULL 6 4 1
最终的结果是:
前序遍历的结果是:1 2 3 7 8 4 5 6
中序遍历的结果是:7 3 8 2 1 5 4 6
后续遍历的结果是:7 8 3 2 5 6 4 1
构建一棵树
BTNode* CreateTree()
{
BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
assert(n1);
BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
assert(n2);
BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
assert(n3);
BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
assert(n4);
BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
assert(n5);
BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
assert(n6);
BTNode* n7 = (BTNode*)malloc(sizeof(BTNode));
assert(n7);
BTNode* n8 = (BTNode*)malloc(sizeof(BTNode));
assert(n8);
n1->data = 1;
n2->data = 2;
n3->data = 3;
n4->data = 4;
n5->data = 5;
n6->data = 6;
n7->data = 7;
n8->data = 8;
n1->left = n2;
n1->right = n4;
n2->left = n3;
n2->right = NULL;
n4->left = n5;
n4->right = n6;
n3->left = NULL;
n3->right = NULL;
n5->left = NULL;
n5->right = NULL;
n6->left = NULL;
n6->right = NULL;
n3->left = n7;
n7->left = NULL;
n7->right = NULL;
n3->right = n8;
n8->left = NULL;
n8->right = NULL;
return n1;//返回根
}
计算树节点总的个数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left) + TreeSize(root->right) + 1;
//如果是空我就返回0,不是我就返回左子树节点的个数加右子树节点的个数加自己
}
二叉树节点总的个数=左子树节点的个数+右子树节点的个数+自己:左子树
节点1走到节点2,节点2走到节点3,节点3走到节点7和节点8,节点3的左边和右边都是1返回2给自己,节点2的左边是节点3右边是0,返回2+自己=3给节点2,节点2的左边是节点3,右边是0,返回3+自己=4给节点1,左子树的节点个数为4;右子树节点1走到节点4,节点4走到节点5和节点6返回2给节点4,节点4返回3给节点1,右子树的节点个数就是3,所以总的节点个数就是4+3+1=8
计算叶子节点的个数
int TreeLeafSzie(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSzie(root->left) + TreeLeafSzie(root->right);
}
先计算左子树 ,节点1走到节点2,节点2走到节点3,节点3走到节点7和节点8,各返回1,,节点3拿到结果左右各返回一个1,一共返回2,节点3回退到节点2再回退到节点1,告诉节点1我的左子树有2个叶子节点,注意:return 1不是直接返回给最外面而是返回给上一层(也就是它调用的地方)然后计算右子树,节点1走到节点4,节点4走到节点5返回1,走到节点6又返回1,一共返回2,节点4再回退给节点1,告诉节点1我的右子树有2个叶子节点。
计算树的高度
int TreeHeight(BTNode* root)
{
//父亲的高度=左右子树大的那个+1
if (root == NULL)
return 0;
int lh = TreeHeight(root->left);
int rh = TreeHeight(root->right);
return lh > rh ? lh + 1 : rh + 1;
}
二叉树的高度=左右子树大的那颗树+1:这个问题我们还可以用分治法,把它看出子树问题,节点1走到节点2,节点2走到节点3,节点3走到节点7和节点8各返回都是1,所以还是返回1给节点3,节点3返回2给节点2,节点2再返回3给节点1,节点1最后返回4我们就求出了左子树的高度,在求右子树的高度:节点1走到节点4,节点4走到节点5和节点6各返回1,1和1一样大还是返回1给节点4,节点4返回2给节点1,节点1最后返回3,因为左子树的高度4大于右子树的高度,所以树的高度就是4,这里最后返回的时候加了1,所以就不加1了,有的小伙伴可能不清楚!
代码的实现:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//给int起一个BTDateType的别称,及BTDataType就是int
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;//结构体的数据
struct BinaryTreeNode* left;//结构体成员变量
struct BinaryTreeNode* right;//结构体成员变量
}BTNode;//二叉树的别名,代表这个结构体
// 二叉树前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);//访问根节点
PreOrder(root->left);//d=递归左子树
PreOrder(root->right);//递归右子树
}
// 二叉树中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);//访问根节点
InOrder(root->right);
}
// 二叉树后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);//d=递归左子树
PostOrder(root->right);//递归右子树
printf("%d ", root->data);//访问根节点
}
//遍历计数
// int count = 0;
// //static局部定义的变量只有在第一次调用才会被初始化,因此放在全局变量
//void TreeSize(BTNode* root)
//{
// if (root == NULL)
// return;
// ++count;
// TreeSize(root->left);
// TreeSize(root->right);
// return ;
//}
//子问题思路解决
//计算节树节点总的个数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left) + TreeSize(root->right) + 1;
//如果是空我就返回0,不是我就返回左子树节点的个数加右子树节点的个数加自己
}
//计算叶子节点的个数
int TreeLeafSzie(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSzie(root->left) + TreeLeafSzie(root->right);
}
//计算树的高度
int TreeHeight(BTNode* root)
{
//父亲的高度=左右子树大的那个+1
if (root == NULL)
return 0;
int lh = TreeHeight(root->left);
int rh = TreeHeight(root->right);
return lh > rh ? lh + 1 : rh + 1;
}
//构建一棵树
BTNode* CreateTree()
{
BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
assert(n1);
BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
assert(n2);
BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
assert(n3);
BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
assert(n4);
BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
assert(n5);
BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
assert(n6);
BTNode* n7 = (BTNode*)malloc(sizeof(BTNode));
assert(n7);
BTNode* n8 = (BTNode*)malloc(sizeof(BTNode));
assert(n8);
n1->data = 1;
n2->data = 2;
n3->data = 3;
n4->data = 4;
n5->data = 5;
n6->data = 6;
n7->data = 7;
n8->data = 8;
n1->left = n2;
n1->right = n4;
n2->left = n3;
n2->right = NULL;
n4->left = n5;
n4->right = n6;
n3->left = NULL;
n3->right = NULL;
n5->left = NULL;
n5->right = NULL;
n6->left = NULL;
n6->right = NULL;
n3->left = n7;
n7->left = NULL;
n7->right = NULL;
n3->right = n8;
n8->left = NULL;
n8->right = NULL;
return n1;//返回根
}
int main()
{
BTNode* root = CreateTree();//根节点
PreOrder(root);
printf("\n");
InOrder(root);
printf("\n");
PostOrder(root);
printf("\n");
/*count = 0;
TreeSize(root);
printf("Tree size:%d\n", count);
count = 0;
TreeSize(root);
printf("Tree size:%d\n", count);*/
printf("Tree size:%d\n", TreeSize(root));
printf("Tree size:%d\n", TreeSize(root));
printf("Tree size:%d\n", TreeSize(root));
printf("Tree leaf size:%d\n", TreeLeafSzie(root));
printf("Tree Height size:%d\n", TreeHeight(root));
return 0;
}
运行结果可以看出除了打印前序、中序和后序,我们构建的二叉树的高度是4,树叶子节点的个数是4,数叶子结点总的个数是8!