数据结构——链式二叉树的实现(详解)

呀哈喽。我是结衣。
不知道大家的递归学到怎么样呢?如果大家的递归功底不是很好,那么我相信在学完这篇文章后大家一定会对递归有一个更深层次的了解的。

构造链式二叉树

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

结构体的创建

typedef int BTdatatype;
typedef struct BinaryTreeNode
{BTdatatype data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}Treenode;

相信学了那么多数据结构的大家一定对此不会陌生,这里的结构体的创建的链表是相似的,毕竟这里的二叉树也是链式结构的。

函数的声明

Treenode* BTTreeCreat(BTdatatype x);
//前序
void PreOrder(Treenode* root);
//中序
void MidOrder(Treenode* root);
//求树的节点个数
int treesize(Treenode* root);
//求叶节点的个数
int TreeLeafSize(Treenode* root);
//求树的高度
int Treehight(Treenode* root);
//第k层的节点个数
int TreeLevelK(Treenode* root, int k);
//销毁
void treeDestroy(Treenode* root);
//查找
Treenode* BinaryTreeFind(Treenode*root,BTdatatype x);

没错学习二叉树就是为了让我们能够实现上面的功能。

构建二叉树

就想前文讲到的那样,现在我先面向结果构造一个如下图所示的二叉树出来,降低学习的成本,然后运用这棵树来实现二叉树的功能。
在这里插入图片描述

创造节点

Treenode* BTTreeCreat(BTdatatype x)
{Treenode* newnode = (Treenode*)malloc(sizeof(Treenode));newnode->data = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}

这个节点的创造和链表一模一样。不懂的小伙伴可以去看我实现链表的那篇文章。
节点创建完了,我们直接面向结果构造一个二叉树(如上图)

Treenode* root1 = BTTreeCreat(1);Treenode* root2 = BTTreeCreat(2);Treenode* root3 = BTTreeCreat(3);Treenode* root4 = BTTreeCreat(4);Treenode* root5 = BTTreeCreat(5);Treenode* root6 = BTTreeCreat(6);//Treenode* root7 = BTTreeCreat(7);root1->left = root2;root1->right = root4;root2->left = root3;root4->left = root5;root4->right = root6;//root5->right = root7;

构造完这个二叉树后我们就要来逐一的实现他的功能了。

函数的实现

我们要实现的功能有:打印二叉树的前序、中序,求二叉树的节点个数,求二叉树的叶节点个数,求二叉树的高度,求第k层的节点个数。

二叉树前序遍历

在实现这功能之前,我们先来了解一下上面是前序、中序、后序:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
    简洁一点就是
    前序的访问顺序为根 左 右
    中序的访问顺序为左 根 右
    后序的访问顺序为左 右 根
    了解完毕,看代码
void PreOrder(Treenode* root)
{if (root == NULL){printf("N ");return;}else{printf("%d ", root->data);PreOrder(root->left);PreOrder(root->right);}
}

发现是递归了吗?没错要实现链式二叉树的遍历就是要用递归的。我们来分析一下这个代码:
递归的结束条件 节点为空时就返回
在这里插入图片描述
当我们进入函数1时,肯定是直接进入else中然后打印1,往下走,将root的左子树传入同函数,第二次进函数2打印2,再将2的左子树传进函数,再进入函数3打印3。这里要注意了,3的左子树为空,所以我们进入函数4的if里面打印N,然后再返回函数3,返回函数3后由于函数3还没执行完就继续往下执行,将3的右子树传给函数3-1,为空打印N,然后返回函数3此时函数3执行完毕退出函数3到函数2。后面差不多就略了,我们来画图看看。
在这里插入图片描述
就是这么调用的。
下面看看打印效果
在这里插入图片描述
和我们分析的一样。

中序遍历

中序遍历只需要把打印放再中间就可以了。

void MidOrder(Treenode* root)
{if (root == NULL){printf("N ");return;}else{MidOrder(root->left);printf("%d ", root->data);MidOrder(root->right);}
}

在这里插入图片描述
这就是它的打印结果大家可以自己分析分析。

求树的节点个数

求节点的个数,我们肯定就要遍历二叉树,所以肯定要用到递归(下面的函数实现也都要用到递归)
首先我们要确定递归的返回条件,当节点为空时我们就返回,并且我要返回0.

int treesize(Treenode* root)
{if (root == NULL){return 0;}return treesize(root->left) + treesize(root->right) + 1;
}

这个代码的本质其实就是,如果该节点不为空就加1,为空就加0。因为在我们调用的每个函数里面只要不是空节点,那么它就会加1然后返回,假设我们已经进入3节点了,当我们继续执行函数的时候,它将3的左右子树再次进入函数,因为左右子树为0。返回0回到3节点再加1回到节点2然后同上。最后我们就能遍历二叉树求的节点的个数了。

在这里插入图片描述

求叶节点的个数

叶节点就是指:没有左右子树的节点。
实现前我们肯定要确定递归停止的条件。当节点为空时返回0。那么我们应该知道当当叶节点进入函数的时候叶要返回,这次返回1。然后就是递归调用。

int TreeLeafSize(Treenode* root)
{if (root == NULL){return 0;}if (!root->left && !root->right)return 1;elsereturn TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

在这里插入图片描述
打印结果也是没有问题的。

求树的高度

要想求树的高度,我们可以这要求:

int Treehight(Treenode* root)
{if (root == NULL)return 0;int lefthight = Treehight(root->left) + 1;int righthight = Treehight(root->right) + 1;return lefthight > righthight ? lefthight : righthight;/*int lefthight = Treehight(root->left);int righthight = Treehight(root->right);return lefthight > righthight ? lefthight+1: righthight+1;*///return fmax()
}

求树的高度本质上就是找最长的一条线路,我们利用lefthight来保存左边最长的线路,用righthight来保存右边最长的线路,最后利用三目操作符的性质来返回最长的一条线路。
在这里插入图片描述
我们还可以再加一个节点7放在5节点的右边再验证一次。
在这里插入图片描述
事实上没有问题。

求第k层的节点个数

这个可以说是我们目前实现函数里面最难想到的一种了。首先我们要想怎么把他拆分成一个个的子问题。再把那个图取过来看看。
在这里插入图片描述
假设我们要求的是第3层节点的个数,是不是可以转换成2,3节点的第二层。3,5,6节点的第一层。有了这个思路来实现起来就会变得容易了。

int TreeLevelK(Treenode* root, int k)
{assert(k > 0);if (root == NULL)return 0;if (k == 1)return 1;elsereturn TreeLevelK(root->left, k - 1) + TreeLevelK(root->right, k - 1);}

我们通过将k不断地减小,将问题的难度逐渐的压缩。最终得到一个不能再压缩的子问题,这就是递归的本质!
看看实现的效果。
我们来求第3层的节点个数(前文我加了一个节点进去,所以二叉树一共是有7个节点的)
在这里插入图片描述

在这里插入图片描述
没有问题,下面我就把我的原码放再下面了

代码

tree.h

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
typedef int BTdatatype;
typedef struct BinaryTreeNode
{BTdatatype data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}Treenode;Treenode* BTTreeCreat(BTdatatype x);
//前序
void PreOrder(Treenode* root);
//中序
void MidOrder(Treenode* root);
//求树的节点个数
int treesize(Treenode* root);
//求叶节点的个数
int TreeLeafSize(Treenode* root);
//求树的高度
int Treehight(Treenode* root);
//第k层的节点个数
int TreeLevelK(Treenode* root, int k);
//销毁
void treeDestroy(Treenode* root);

tree.c

#define _CRT_SECURE_NO_WARNINGS
#include "tree.h"Treenode* BTTreeCreat(BTdatatype x)
{Treenode* newnode = (Treenode*)malloc(sizeof(Treenode));newnode->data = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}
void treeDestroy(Treenode* root)
{assert(root);}
void PreOrder(Treenode* root)
{if (root == NULL){printf("N ");return;}else{printf("%d ", root->data);PreOrder(root->left);PreOrder(root->right);}
}
void MidOrder(Treenode* root)
{if (root == NULL){printf("N ");return;}else{MidOrder(root->left);printf("%d ", root->data);MidOrder(root->right);}
}int treesize(Treenode* root)
{if (root == NULL){return 0;}return treesize(root->left) + treesize(root->right) + 1;
}int TreeLeafSize(Treenode* root)
{if (root == NULL){return 0;}if (!root->left && !root->right)return 1;elsereturn TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
int Treehight(Treenode* root)
{if (root == NULL)return 0;int lefthight = Treehight(root->left) + 1;int righthight = Treehight(root->right) + 1;return lefthight > righthight ? lefthight : righthight;/*int lefthight = Treehight(root->left);int righthight = Treehight(root->right);return lefthight > righthight ? lefthight+1: righthight+1;*///return fmax()
}int TreeLevelK(Treenode* root, int k)
{assert(k > 0);if (root == NULL)return 0;if (k == 1)return 1;elsereturn TreeLevelK(root->left, k - 1) + TreeLevelK(root->right, k - 1);}

test.c

#include "tree.h"int main()
{Treenode* root1 = BTTreeCreat(1);Treenode* root2 = BTTreeCreat(2);Treenode* root3 = BTTreeCreat(3);Treenode* root4 = BTTreeCreat(4);Treenode* root5 = BTTreeCreat(5);Treenode* root6 = BTTreeCreat(6);Treenode* root7 = BTTreeCreat(7);root1->left = root2;root1->right = root4;root2->left = root3;root4->left = root5;root4->right = root6;root5->right = root7;PreOrder(root1);printf("\n");MidOrder(root1);printf("\n");printf("treesize:%d", treesize(root1));printf("\n");printf("treeleafsize:%d\n", TreeLeafSize(root1));printf("treehight:%d\n", Treehight(root1));printf("treelevelk:%d\n", TreeLevelK(root1, 3));return 0;
}


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/228750.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常见气体爆炸下限

国标中关于常见气体爆炸下限说明&#xff0c;特此记录 特此记录 anlog 2023年11月29日

java编程:数组代表着苹果期货在 9 天内的价格变化,其中第 n 个元素是苹果在第 n 天的 价格,你可以进行最多三次交易,设计算法,9 天内赚到最多的钱

1 题目 编程题&#xff1a;下面的数组代表着苹果期货在 9 天内的价格变化&#xff0c;其中第 n 个元素是苹果在第 n 天的 价格&#xff0c;你可以进行最多三次交易&#xff0c;设计一个算法&#xff0c;9 天内如何赚到最多的钱&#xff1f;价格数组&#xff1a; [8, 9, 2, 5, …

面试必须要知道的MySQL知识--索引

10 索引 10.1 数据页存储结构 10.1.1 数据页的各个部分 在讲索引之前&#xff0c;让我们看看一个单独的数据页是什么样子的 去除掉一些我们不太需要那么关注的部分后&#xff0c;简化如下&#xff1a; 也就是说平时我们在一个表里插入的一行一行的数据会存储在数据页里&#…

Blender学习笔记:小车狂奔动画

文章目录 路旁小树汽车尾气移动 教程地址&#xff1a;八个案例教程带你从0到1入门blender【已完结】 小车建模 路旁小树 1 添加摄像机&#xff0c;在小车下面拉一个平面&#xff0c;覆盖到摄像机的观察视窗。复制一层平面&#xff0c;收窄变成小车两侧的路面&#xff0c;编辑…

VAE模型及pytorch实现

VAE模型及pytorch实现 VAE模型推导部分最小化KL散度推导代码部分损失函数Encoder部分Decoder部分VAE整体架构 VAE问题参考资料 VAE&#xff08;变分自编码器&#xff09;是一种生成模型&#xff0c;结合了自编码器和概率图模型的思想。它通过学习数据的潜在分布&#xff0c;可以…

Python面试破解:return和yield的细腻差别

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是涛哥&#xff0c;今天为大家分享 Python面试破解&#xff1a;return和yield的细腻差别&#xff0c;全文3000字&#xff0c;阅读大约10钟。 在Python的函数编程中&#xff0c;return和yield是两个常用的关键词…

居家适老化设计第三十四条---卫生间之照明

居家适老化卫生间照明设计需要考虑以下几个方面&#xff1a;1. 光源选择&#xff1a;选择适合老年人眼睛的柔和光源&#xff0c;避免刺眼和眩光的发生。可以选择LED灯具&#xff0c;因为它们具有节能、寿命长和可调光的特点。2. 光线布置&#xff1a;在不同区域设置不同的光线&…

Redis深入理解-主从架构下内核数据结构、主从同步以及主节点选举

Redis 主从挂载后的内核数据结构分析 主节点中&#xff0c;会通过 clusteNode 中的 slaves 来记录该主节点包含了哪些从节点&#xff0c;这个 slaves 是一个指向 *clusterNode[] 数组的数据结构从节点中&#xff0c;会通过 clusterNode 中的 slaveof 来记录该从节点属于哪个主…

vue3中的Fragment、Teleport、Suspense新组件

Fragment组件 在Vue2中: 组件必须有一个根标签 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中 好处: 减少标签层级, 减小内存占用 <template><div style"font-size: 14px;"><p> 组件可以没有根标签</p&g…

【JUC】十四、synchronized进阶

文章目录 1、synchronized2、synchronized与monitor3、管程Monitor4、Q&#xff1a;为什么每个Java对象都可以成为一个锁&#xff1f;5、小结 1、synchronized 写个demo&#xff0c;具体演示下对象锁与类锁&#xff0c;以及synchronized同步下的几种情况练习分析。demo里有资源…

Notion for Mac:打造您的专属多功能办公笔记软件

在如今这个信息爆炸的时代&#xff0c;一款高效、便捷的笔记软件对于办公人士来说已经成为必不可少的工具。Notion for Mac&#xff0c;作为一款多功能办公笔记软件&#xff0c;凭借其简洁优雅的界面、强大的功能以及无缝的云端同步&#xff0c;成为了众多用户的首选。 一、多…

LD_PRELOAD劫持

LD_PRELOAD劫持 <1> LD_PRELOAD简介 LD_PRELOAD 是linux下的一个环境变量。用于动态链接库的加载&#xff0c;在动态链接库的过程中他的优先级是最高的。类似于 .user.ini 中的 auto_prepend_file&#xff0c;那么我们就可以在自己定义的动态链接库中装入恶意函数。 也…