【数据结构】二叉树 链式结构的相关问题

 本篇文章来详细介绍一下二叉树链式结构经常使用的相关函数,以及相关的的OJ题。

目录

1.前置说明

2.二叉树的遍历

2.1 前序、中序以及后序遍历

2.2 层次遍历

3.节点个数相关函数实现

3.1 二叉树节点个数

3.2 二叉树叶子节点个数

3.3 二叉树第k层节点个数

3.4 在二叉树中查找值为x的节点

4.二叉树基础oj练习

5.二叉树的创建和销毁

5.1 通过前序遍历构建二叉树

5.2 销毁二叉树

5.3 判断二叉树是否为完全二叉树


1.前置说明

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

typedef int BTDataType;typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;//创造树节点
BTNode* BuyNode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;} newnode->data = x;newnode->left = newnode->right = NULL;return newnode;
}// 构建二叉树
BTNode* CreatBinaryTree()
{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->Right = node5;node4->Left = node6;return node1;
}

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后面详解重点讲解。

再看二叉树基本操作前,再回顾下二叉树的概念二叉树是:

  1. 空树
  2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

创建的二叉树图解: 

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

2.二叉树的遍历

2.1 前序、中序以及后序遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
 

 按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal亦称先序遍历)――访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);

三个函数实现起来非常相似,只是访问数据的顺序不同。

具体实现:

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{if (root==NULL){printf("# ");return;}printf("%c ",root->data);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){printf("#");return;}BinaryTreePrevOrder(root->left);printf("%c ", root->data);BinaryTreePrevOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){printf("#");return;}BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);printf("%c ", root->data);
}

下面主要分析前序递归遍历,中序与后序图解类似,大家可自己动手绘制。

前序遍历递归图解:

 

前序遍历结果:1 2 3 4 5 6

中序遍历结果:3 2 1 5 4 6

后序遍历结果:3 2 5 6 4 1
 

2.2 层次遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

 那么我们怎么实现呢?

这里需要使用队列,让根节点先入堆,再出队,出队时让左右子树入堆,空树不进队,按照这个方式可以实现二叉树的层次遍历。

具体实现:这里队列相关函数要自己实现,C++就方便多了,以后会讲。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{//创建一个队列Queue q;//初始化队列QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%c ", front->data);if (front->left){QueuePush(&q, front->left);}if (front->right){QueuePush(&q, front->right);}}printf("\n");QueueDestroy(&q);
}

3.节点个数相关函数实现

3.1 二叉树节点个数

=左子树的节点数+右子树的节点数+根节点数。根节点数为1。

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

3.2 二叉树叶子节点个数

=左子树的叶子节点数+右子树的叶子节点数。

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->right == NULL && root->left == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

3.3 二叉树第k层节点个数

=左子树的K-1层节点数+右子树的K-1层节点数。

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{assert(k > 0);if (root == NULL){return 0;}if (k == 1){return 1;}return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

3.4 在二叉树中查找值为x的节点

=根节点不是,就在左子树和右子树中寻找

//在二叉树中查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}BTNode* left = BinaryTreeFind(root->left, x);BTNode* right = BinaryTreeFind(root->right, x);return left == NULL ? right : left;
}

4.二叉树基础oj练习

  1. 单值二叉树。OJ链接
  2. 检查两颗树是否相同。OJ链接
  3. 对称二叉树。OJ链接
  4. 二叉树的前序遍历。OJ链接
  5. 二叉树中序遍历。OJ链接
  6. 二叉树的后序遍历。OJ链接
  7. 另一颗树的子树。OJ链接

5.二叉树的创建和销毁

二叉树的构建及遍历。OJ链接

5.1 通过前序遍历构建二叉树

通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树,'#'代表空。

代码实现:

//开辟树节点空间
BTNode* BuyNode(char x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));newnode->data = x;newnode->left = newnode->right = NULL;return newnode;
}
//构建树
BTNode* CreatTree(char* arr,int*i)
{if(arr[*i] =='#'){(*i)++;return NULL;}BTNode* node = BuyNode(arr[*i]);(*i)++;node->left = CreatTree(arr,i);node->right = CreatTree(arr,i);return node;
}int main() 
{char arr[] = "ABD##E#H##CF##G##";int i = 0;//传递下标的地址,这样就可以通过地址修改下标。BTNode* tree = CreatTree(arr, &i);return 0;
}

5.2 销毁二叉树

这里是后序思想,先释放左右子树,最后释放根节点。

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{if (*root == NULL){return;}BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right)); free(*root);*root = NULL;
}

5.3 判断二叉树是否为完全二叉树

这里也是通过队列进行判断,之前层次遍历空树不进队,而这里空树进队,当出队时遇到空时,停止出队,判断队列中是否有非空,如果有就不是完全二叉树

代码实现:

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);if (front != NULL){QueuePop(&q);QueuePush(&q, front->left);QueuePush(&q, front->right);}else{//遇到空就跳出break;}}//检查后面节点有没有非空//有非空就不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front != NULL) return 0;//不是}return 1;//是
}

本篇结束!

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

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

相关文章

15. Canvas制作汽车油耗仪表盘

1. 说明 本篇文章在14. 利用Canvas组件制作时钟的基础上进行一些更改,想查看全面的代码可以点击链接查看即可。 效果展示: 2. 整体代码 import QtQuick 2.15 import QtQuick.Controls 2.15Item{id:rootimplicitWidth: 400implicitHeight: implicitWi…

分布式事务理论基础

今天啊,本片博客我们一起来学习一下微服务中的一个重点和难点知识:分布式事务。 我们会基于Seata 这个框架来学习。 1、分布式事务问题 事务,我们应该比较了解,我们知道所有的事务,都必须要满足ACID的原则。也就是 …

Lnton羚通算法算力云平台【PyTorch】教程:torch.nn.Softsign

torch.nn.Softsign 原型 CLASS torch.nn.Softsign() 图 代码 import torch import torch.nn as nnm nn.Softsign() input torch.randn(4) output m(input)print("input: ", input) print("output: ", output)# input: tensor([ 0.0046, -0.4135, -2…

积跬步至千里 || 矩阵可视化

矩阵可视化 矩阵可以很方面地展示事物两两之间的关系,这种关系可以通过矩阵可视化的方式进行简单监控。 定义一个通用类 from matplotlib import pyplot as plt import seaborn as sns import numpy as np import pandas as pdclass matrix_monitor():def __init…

详解Spring的循环依赖问题、三级缓存解决方案源码分析

0、基础:Bean的生命周期 在Spring中,由于IOC的控制反转,创建对象不再是简单的new出来,而是交给Spring去创建,会经历一系列Bean的生命周期才创建出相应的对象。而循环依赖问题也是由Bean的生命周期过程导致的问题&#…

API 接口选择那个?RESTful、GraphQL、gRPC、WebSocket、Webhook

大家好,我是比特桃。目前我们的生活紧紧地被大量互联网服务所包围,互联网上每天都有数百亿次API调用。API 是两个设备相互通讯的一种方式,人们在手机上每次指尖的悦动,背后都是 API 接口的调用。 本文将列举常见的一些 API 接口&…

深度学习3:激活函数

一、激活函数的简介与由来 激活函数:是用来加入非线性因素的,解决线性模型所不能解决的问题。 线性函数的组合解决的问题太有限了,碰到非线性问题就束手无策了。如下图。 通过激活函数映射之后,可以输出非线性函数。 最后再通过…

学习maven工具

文章目录 🐒个人主页🏅JavaEE系列专栏📖前言:🏨maven工具产生的背景🦓maven简介🪀pom.xml文件(project object Model 项目对象模型) 🪂maven工具安装步骤两个前提:下载 m…

CTF-REVERSE练习之病毒分析

一、实验目的: 1)了解CTF比赛中逆向分析的目的 2)掌握7zip工具的使用 3)掌握在线沙箱的基本使用方法 二、实验过程: 首先,对这个IMG文件用7zip打开 通过对这个“是男人.exe”使用7zip打开,得…

基于 BlockQueue(阻塞队列) 的 生产者消费者模型

文章目录 阻塞队列(BlockQueue)介绍生产者消费者模型 介绍代码实现lockGuard.hpp()Task.hpp(任务类)BlockQueue.hpp(阻塞队列)conProd.cc(生产者消费者模型 主进程&#…

【C++】—— 详解AVL树

目录 序言 (一)AVL树的概念 1、AVL树的由来 2、AVL树的特点 3、平衡因子 (二)AVL树的插入 1、插入操作的思想理解 2、AVL树的旋转 1️⃣ LL平衡旋转(右单旋转) 2️⃣ RR平衡旋转(左单…

Java请求Http接口-OkHttp(超详细-附带工具类)

简介:OkHttp是一个默认有效的HTTP客户端,有效地执行HTTP可以加快您的负载并节省带宽,如果您的服务有多个IP地址,如果第一次连接失败,OkHttp将尝试备用地址。这对于IPv4 IPv6和冗余数据中心中托管的服务是必需的。OkHt…