【数据结构】二叉树遍历的实现(超详细解析,小白必看系列)_从此节点开始遍历二叉树

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 代码演示:

中序遍历的代码和前序遍历一样,看起来都非常简洁:

// 中序遍历  左 根 右
void InOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL "); // 如果为空,就打印
		return;      // 当前函数结束
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

  • 效果如下:

单纯看代码看不出啥头绪,还得是画递归图。

💦后续遍历

  • 遍历规则:

后续遍历,也叫后根遍历

**遍历顺序:**左子树 -> 右子树 -> 根结点

  • 思路:

要访问1得先访问1的左子树2,继而得先访问2的左子树3,再先访问3的左子树NULL,右子树NULL,根3,递归返回访问2的右子树NULL,根2,再递归返回访问1的右子树……类似的

  • 画图演示:

  • 代码演示:
// 后序 左 右 根
void PosOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL "); // 如果为空,就打印
		return;      // 当前函数结束
	}
	PosOrder(root->left);
	PosOrder(root->right);
	printf("%d ", root->data);
}
  • 效果如下:

  • 画图演示递归过程:

🍌二叉树的经典问题(重点)

💦二叉树节点个数

  • 思想:

求结点个数,这里我将提供如下几种方法,但不都是可行的,要对比着看,本质都是递归的思想:

  • 法一:遍历

在前文中,我们已经学习了如何遍历链式二叉树,现在想求结点的个数,那么只需要随便采用一种遍历方式,并把打印换成计数++来求个数即可,听起来非常容易,先看代码:

//节点个数
void BTreeSize(BTNode* root)
{
	int count = 0; //局部变量
	if (root == NULL) //如果为空
		return;
	++count;
	BTreeSize(root->left);
	BTreeSize(root->right);
}

难道上述代码能够准确求出结点个数吗?其实不然,根本求不出来。

具体解释起来需要借用栈帧的思想,因为这里用的是递归,而递归是每递归一次在栈帧里头都会开辟一块空间,每一块栈帧都会有一个count,而我希望的是只需要有一个count,然后所有的count均加在一起,可是现在每递归一次,重新开辟一个count,count即局部变量。递归完就销毁,同形参的改变不会影响实参一样,一个道理。所有此法根本就行不通,得换。

  • 法二:定义局部静态变量count

在法一中,我们定义的是局部变量count,会导致每递归一次就开辟栈帧,并创建count,每次递归结束返回就销毁栈帧。那如果可以把count放在静态区里头,不久可以保留住count吗

//节点个数
int BTreeSize(BTNode* root)
{
	static int count = 0; //局部静态变量
	if (root == NULL) //如果为空
		return count;
	++count;
	BTreeSize(root->left);
	BTreeSize(root->right);
	return count;
}
  • 效果如下:

看似好像是成功了,确实结点个数为6,但真的就是成功了吗?当然不是,如果我们现在想多打印几次呢?
什么鬼?怎么size还呈现等差数列递增呢?就是因为这里运用了static关键字,将count扣在静态区,导致多次调用没办法初始化为0,使其每次递归调用累计加加,但是当你再重新调用自己时,count不会重新置为0,会依旧保留为曾经++的结果。局部的静态变量有一个好处,它的生命周期在全局,但是只能在局部去访问。它的初始化为0只有第一次调用会访问,其余均不会。由此可见,局部的静态也是不行的,还得再优化。

  • 法三:定义全局变量count

法二的局部静态变量行不通,那就把count设定为全局变量。要知道全局变量是存在静态区的。虽然也在静态区,但是其初始化为0是可以重复访问的。

//节点个数
int count = 0;
void BTreeSize(BTNode* root)
{
	if (root == NULL) //如果为空
		return;
	++count;
	BTreeSize(root->left);
	BTreeSize(root->right);
}
  • 效果如下:

确实可以求出结点个数,并且也不会出现像法二一样的问题。但是其实定义全局变量也会存在一个小问题:线程安全的问题,这个等以后学到Linux再来讨论,我们这边考虑再换一种更优解。

  • 法四:最优解

我们这里可以考虑多套一层,可以考虑把变量的地址传过去。这样操作不会存在任何问题,上代码:

//节点个数
void BTreeSize(BTNode* root, int* pCount)
{
	if (root == NULL) //如果为空
		return;
	++(*pCount);
	BTreeSize(root->left, pCount);
	BTreeSize(root->right, pCount);
}

确实可以求出结点个数,并且也不会出现像法二一样的问题。但是其实定义全局变量也会存在一个小问题:线程安全的问题,这个等以后学到Linux再来讨论,我们这边考虑再换一种更优解。

  • 法五:新思路

直接利用子问题的思想来写,返回当root为空为0,不是就递归左树+右树+1。

  1. 空树,最小规模子问题,结点个数返回0
  2. 非空,左子树结点个数+右子树结点个数 + 1(自己)
int BTreeSize(BTNode* root)
{
	return root == NULL ? 0 : 
    BTreeSize(root->left) + 
    BTreeSize(root->right) + 1;
}

此法非常巧妙,很灵活的运用了递归的思想,我们通过递归图来深刻理解下:

💦二叉树叶子节点的个数

  • 思路1:遍历+计数

在遍历的基础上如果结点的左右子树均为空则count++。但是此题我们依旧采用分治思想。

// 计算叶子节点个数
// 遍历 + 计数
void TreeLeveSize1(BT* root, int* count)
{
    if(root==NULL)
    {
        return ;
    }
    if(root->left==NULL&&root->right==NULL)
    {
        (*count)++;
    }
    TreeLeveSize1(root->left,count);
    TreeLeveSize1(root->right,count);

}
  • 思路2:遍历+递归
// 叶子节点个数
int TreeLeveSize2(BT*root)
{
    if(root==NULL)
    {
        return 0;
    }
    int left = TreeLeveSize2(root->left);
    int right = TreeLeveSize2(root->right);
    if(left==0&&right==0)
    {
        return 1;
    }
    return left+right;
}
  • 思路3:分治思想

首先,如果为空,直接返回0,如若结点的左子树和右子树均为空,则为叶节点,此时返回1,其它的继续分治递归。

  • 代码演示:
/ 计算叶子节点个数
int TreeLeveSize(BT* root)
{
    if(root==NULL)
    {
        return 0;
    }
    if(root->left==NULL&&root->right==NULL)
    {
        return 1;
    }
    return TreeLeveSize(root->left)+TreeLeveSize(root->right);
}
  • 递归图:

💦二叉树第K层节点个数

  • 思路:

假设K=3

  1. 空树,返回0
  2. 非空,且K == 1,返回1
  3. 非空,且K>1,转换成左子树K-1层节点个数 + 右子树K-1层节点个数
  • 代码演示:
// 树的第K层节点个数
// 1. 当前树的第K层 = 左子树的第K-1层 + 右子树的第K-1层
int TreeKSize(BT* root, int k)
{
	assert(k > 0);
	// 给出 所问 问题 能成成立的条件(所问,问题的的结束条件)
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}

	return TreeKSize(root->left, k - 1) + TreeKSize(root->right, k - 1);

}
  • 递归图:

💦二叉树的深度

  • 思路:

此题同样是运用分治的思想来解决,要比较左子树的高度和右子树的高度,大的那个就+1,因为还有根结点也算1个高度。

  • 代码演示:
// 树的深度
int TreeDeep(BT* root)
{
    if(root==NULL)
    {
        return 0;
    }
    int left = TreeDeep(root->left);
    int right = TreeDeep(root->right);
    if(left>right)
    {
        return left+1;
    }
    else
    {
        return right+1;
    }

}
  • 递归图:

💦二叉树查找值为x的节点

  • 思路:

还是利用分治的思想,将其递归化成子问题去解决

  1. 先去根结点寻找,是就返回此节点
  2. 此时去左子树查找有无节点x
  3. 最后再去右子数去查找有无节点x
  4. 若左右子树均找不到节点x,直接返回空
  • 代码演示:
// 查询 树中的某一个节点是否存在
BT* TreeNode(BT* root,TreeDatatype x)
{
    if(root==NULL)
    {
        return NULL;
    }
    if(root->data==x)
    {
        return root;
    }
    BT* left = TreeNode(root->left,x);
    BT* right = TreeNode(root->right,x);

    if(left!=NULL)
    {
        return left;
    }
    if(right!=NULL)
    {
        return right;
    }
    return NULL;
}
  • 递归图:

假设我们寻找的是数字5

💦二叉树的销毁

  • 思路:

销毁的思想和遍历类似,如若我挨个遍历的同时,没遍历一次就销毁一次,岂不能达到效果,但是又会存在一个问题,那就是你要采用什么样的遍历方式?倘若你采用前序遍历,刚开始就把根销毁了,那么后面的子树还怎么销毁呢?因为此时根没了,子树找不到了就,所以要采用倒着销毁的规则,也就是后续的思想

  • 代码演示:
// 二叉树的销毁
// 从后往前销毁-------后续思想
// 进行遍历然后在销毁
void DestoryTree(BT*root)
{
    if(root==NULL)
    {
        return;
    }
    DestoryTree(root->left);
    DestoryTree(root->right);
    free(root);
}

思想和后续遍历类似,不做递归图演示。

🍊总代码和演示图

💦代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int TreeDatatype;

typedef struct BinaryTree
{
    struct BinaryTree* left;
    struct BinaryTree* right;
    TreeDatatype data;
}BT;


BT* BuyTreenode(TreeDatatype x)
{
    BT* tree = (BT*)malloc(sizeof(BT));

    if(tree==NULL)
    {
        perror(" fail");
        exit(-1);
    }
    tree->data = x;
    tree->left = tree->right = NULL;

    return tree;
}

BT* CreatTree()
{
    BT* node1 = BuyTreenode(1);
    BT* node2 = BuyTreenode(2);
    BT* node3 = BuyTreenode(3);
    BT* node4 = BuyTreenode(4);
    BT* node5 = BuyTreenode(5);
    BT* node6 = BuyTreenode(6);
    //BT* node7 = BuyTreenode(7);

    node1->left = node2;
    node1->right = node4;
    node2->left = node3;
    //node2->right = node7;
    node4->left = node5;
    node4->right = node6;

    return node1;
}


// 前序遍历:根 左 右
void PreOrder(BT* root)
{
    if(root==NULL)
    {
        printf("NULL ");
        return;
    }
    printf("%d ",root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}

// 中序遍历
void InOrder(BT*root)
{
    if(root==NULL)
    {
        printf("NULL");
        return;
    }
    InOrder(root->left);
    printf("%d ",root->data);
    InOrder(root->right);
}


// 后序遍历
void PastOrder(BT* root)
{
    if(root==NULL)
    {
        printf("NULL");
        return;
    }
    PastOrder(root->left);
    PastOrder(root->right);
    printf("%d ",root->data);
}


// 计算一颗树的节点个数
int TreeSize(BT* root)
{
    if(root==NULL)
    {
        return 0;
    }

    return TreeSize(root->left)+TreeSize(root->right)+1;
}

// 计算叶子节点个数
int TreeLeveSize(BT* root)
{
    if(root==NULL)
    {
        return 0;
    }
    if(root->left==NULL&&root->right==NULL)
    {
        return 1;
    }
    return TreeLeveSize(root->left)+TreeLeveSize(root->right);
}

// 计算叶子节点个数
// 遍历 + 计数
void TreeLeveSize1(BT* root, int* count)
{
    if(root==NULL)
    {
        return ;
    }
    if(root->left==NULL&&root->right==NULL)
    {
        (*count)++;
    }
    TreeLeveSize1(root->left,count);
    TreeLeveSize1(root->right,count);

}

// 叶子节点个数
int TreeLeveSize2(BT*root)
{
    if(root==NULL)
    {
        return 0;
    }
    int left = TreeLeveSize2(root->left);
    int right = TreeLeveSize2(root->right);
    if(left==0&&right==0)
    {
        return 1;
    }
    return left+right;
}


// 树的第 K 层的节点
int TreeKLeveSize(BT* root,int k)
{
    if(root==NULL)
    {
        return 0;
    }
    if(k==1)
    {
        return 1;
    }
    return TreeKLeveSize(root->left,k-1)+TreeKLeveSize(root->right,k-1);
}

// 树的深度
int TreeDeep(BT* root)
{
    if(root==NULL)
    {
        return 0;

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

{
    return 1;
}
return left+right;

}

// 树的第 K 层的节点
int TreeKLeveSize(BT* root,int k)
{
if(rootNULL)
{
return 0;
}
if(k
1)
{
return 1;
}
return TreeKLeveSize(root->left,k-1)+TreeKLeveSize(root->right,k-1);
}

// 树的深度
int TreeDeep(BT* root)
{
if(root==NULL)
{
return 0;

[外链图片转存中…(img-zGfesxUr-1715789853712)]
[外链图片转存中…(img-uunZoqzM-1715789853713)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值