数据结构-二叉树

本篇博客我们来仔细说一下二叉树链式存储的的结构,及其一些OJ题目

💓 个人主页:普通young man-CSDN博客

⏩ 文章专栏:数据结构_普通young man的博客-CSDN博客

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章

目录

前言

二叉树的性质

链式二叉树

二叉树的遍历

前,中,后序遍历

实现

前序

中序

后序

层序遍历

二叉树实现

接口

代码实现

分析图:


前言

接着上一篇博客我们现在讲一下链式二叉树,没看上一篇博客的可以回去看一下上一篇博客:

数据结构-堆(带图)详解-CSDN博客

二叉树的性质

1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.
2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是 .
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1
 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2
为底,n+1为对数)
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对
于序号为i的结点有:
1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

下面我们看几个选择题

1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
A 不存在这样的二叉树
B 200
C 198
D 199
 
2.下列数据结构中,不适合采用顺序存储结构的是( )
A 非完全二叉树
B 堆
C 队列
D 栈
 
3.在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
 
A n
B n+1
C n-1
D n/2
 
4.一棵完全二叉树的结点数位为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
 
5.一个具有767个结点的完全二叉树,其叶子结点个数为()

A 383

B 384

C 385

D 386

链式二叉树

        链式二叉树是一种数据结构,用于表示二叉树。在二叉树中,每个节点最多有两个子节点,通常称为左子节点和右子节点。链式存储结构是实现二叉树的一种方式,与之相对的是顺序存储结构。在链式存储结构中,二叉树中的每个节点包含三个部分:节点值、指向左子节点的指针(或引用)和指向右子节点的指针(或引用)。这种结构便于在树中插入、删除节点以及遍历树。

具体来说,一个链式二叉树的节点定义可以如下:

struct TreeNode {
    int val;            // 节点值
    TreeNode *left;     // 指向左子节点的指针
    TreeNode *right;    // 指向右子节点的指针
};

通过这样的结构,可以灵活地构造和操作二叉树。例如,创建一个新节点、将新节点添加为某个节点的子节点、遍历树等操作,都是通过调整这些指针来完成的。

和堆的实现不一样,堆是用顺序实现,二叉树使用链表实现,从这一点你就会发现,我的博客是循序渐进的,想看懂这些,就要搞懂前面的知识

二叉树的遍历

前,中,后序遍历

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

二叉树遍历的三种经典递归策略包括:

  1. 前序遍历(Preorder Traversal):在此方法中,节点的访问操作优先于对其左右子树的遍历,遵循“根-左-右”的顺序,简称为NLR遍历。

  2. 中序遍历(Inorder Traversal):节点访问发生在探索其左子树之后,右子树之前,体现了“左-根-右”的访问模式,称为LNR遍历。

  3. 后序遍历(Postorder Traversal):最晚访问的是当前节点,即在完全遍历左右子树后,遵循“左-右-根”的顺序,记作LRN遍历。

这三种遍历方式,从根节点出发,以不同的顺序探索整个二叉树结构,其中N、L、R分别象征着访问节点(Node)、左子树(Left subtree)和右子树(Right subtree),因此也常被描述为先根遍历、中根遍历和后根遍历,分别强调了处理当前节点的时机。

// 二叉树前序遍历 
void PreOrder(BTNode* root);
// 二叉树中序遍历
void InOrder(BTNode* root);
// 二叉树后序遍历
void PostOrder(BTNode* root);


实现

递归代码展开图+递归图

前序
// 二叉树前序遍历 
void PreOrder(TNode* root) {
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
中序
// 二叉树中序遍历
void InOrder(TNode* root) {
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}
后序
// 二叉树后序遍历
void PostOrder(TNode* root) {
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

层序遍历

层序遍历(Level Order Traversal)是对二叉树的一种遍历方式,不同于先序、中序和后序遍历的递归或深度优先策略,它采用广度优先搜索(BFS)策略进行。具体步骤如下:

  1. 初始化:从根节点开始,将根节点放入一个队列中。
  2. 遍历过程
    • 取出队列中的第一个节点进行访问。
    • 若该节点有左孩子,将左孩子加入队列。
    • 若该节点有右孩子,将右孩子加入队列。
    • 重复上述步骤,直到队列为空,遍历结束。
  3. 访问顺序:层序遍历保证了同一层的节点按照从左到右的顺序被访问,而不同层之间的访问则是从上到下进行。

这种遍历方式能够直观地展现二叉树每一层级的节点分布,对于需要按层次处理数据结构的场景尤为适用,如判断二叉树是否为完全二叉树、打印特定形状的二叉树图形等。

那我们如何实现层序遍历?看gif

#include"Queue.h"
//层序遍历
void LevelOrder(TNode* root) {
	Queue s1;
	Q_Init(&s1);
	if (root)
	{
		Q_Push(&s1, root);
	}
	while (!Q_Empty(&s1))
	{
		TNode* Front = Q_Front(&s1);
		Q_Pop(&s1);
		printf("%d ", Front->data);
		if (Front->left)
			Q_Push(&s1, Front->left);
		if (Front->right)
			Q_Push(&s1, Front->right);

	}

	Q_Destroy(&s1);

}

这边需要引用一下队列的头文件

Leetcode-用栈实现队列(图解)-CSDN博客文章浏览阅读617次,点赞47次,收藏32次。232. 用栈实现队列 - 力扣(LeetCode)https://blog.csdn.net/2302_78381559/article/details/139132223?spm=1001.2014.3001.5501

#pragma once
/*--头文件--*/
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef struct TreeNode* DataType;
//创建队列结构
typedef struct QueueNode
{
	struct QueueNode* Next;
	DataType val;
}QueueNode;
//创建一个结构体来确定头/尾,可以避免使用二级指针,也可以用哨兵来避免使用二级指针
typedef struct Queue {
	QueueNode* head;//头
	QueueNode* tail;//尾
	int size;//计数
}Queue;

...

层序遍历写完了,我们扩展一个,判断二叉树是否是完全二叉树

如何判断是否是一个完全二叉树嘞?

// 函数声明:判断一个二叉树(由TNode指针root表示)是否是完全二叉树
bool BinaryTreeComplete(TNode* root) {
    // 初始化一个队列s1,用于层次遍历二叉树
    Queue s1;
    Q_Init(&s1);

    // 如果根节点不为空,将其入队
    if (root)
        Q_Push(&s1, root);

    // 当队列非空时执行循环
    while (!Q_Empty(&s1)) {
        // 弹出队列首部的节点(当前层的节点)
        TNode* Front = Q_Front(&s1);
        Q_Pop(&s1);

        // 如果当前节点为空,表示已到达某层的末尾
        if (Front == NULL)
            break;

        // 非空节点,将其左右孩子依次入队,以便后续遍历
        Q_Push(&s1, Front->left);
        Q_Push(&s1, Front->right);
    }

    // 检查后续节点是否都为空,确保一旦遇到空节点后其余皆为空
    while (!Q_Empty(&s1)) {
        TNode* Front_s = Q_Front(&s1);
        Q_Pop(&s1);
        // 如果遇到非空节点,说明违反完全二叉树规则,返回false
        if (Front_s != NULL) 
            return false;
    }

    // 队列遍历完毕且通过检查,说明是完全二叉树,销毁队列并返回true
    Q_Destroy(&s1);
    return true;
}

二叉树实现

接口

// 二叉树结点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
// 层序遍历
void LevelOrder(BTNode* root);

代码实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


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


 //创建二叉树函数,添加内存分配检查

  

//创建一个链式结构的二叉树
typedef int Type_Date;
typedef struct TreeNode
{
	struct TreeNode* left;
	struct TreeNode* right;
	Type_Date data;
}TNode;

TNode* BuyNode(Type_Date x) {
	TNode* newnode = (TNode*)malloc(sizeof(TNode));
	if (newnode == NULL)
	{
		assert("malloc");
		return NULL;
	}
	newnode->data = x;
	newnode->left = newnode->right = NULL;

	return newnode;
}


TNode* CreatBinaryTree()
{
	TNode* node1 = BuyNode(1);
	TNode* node2 = BuyNode(2);
	TNode* node3 = BuyNode(3);
	TNode* node4 = BuyNode(4);
	TNode* node5 = BuyNode(5);
	TNode* node6 = BuyNode(6);
	TNode* node7 = BuyNode(7);


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

	return node1;
}

// 二叉树前序遍历 
void PreOrder(TNode* root) {
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
// 二叉树中序遍历
void InOrder(TNode* root) {
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}
// 二叉树后序遍历
void PostOrder(TNode* root) {
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

// 二叉树结点个数
int BinaryTreeSize(TNode* root) {
	if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子结点个数
int BinaryTreeLeafSize(TNode* root) {
	//首先判空:
	//1、判断是否一个节点都没有。 2、处理一个节点返回的叶子的个树,一个节点返回NULL
	if (root == NULL)
	{
		return 0;
	}
	//判断左右都为NULL,才是叶子
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(TNode* root, int k) {
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}


// 二叉树查找值为x的结点
TNode* BinaryTreeFind(TNode* root, Type_Date x) {
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	TNode* root_left =  BinaryTreeFind(root->left,x);
	if (root_left)
	{
		return root_left;
	}
	TNode* root_right = BinaryTreeFind(root->right,x);//存一下返回值
	//判断返回值
	if (root_right)
	{
		return root_right;
	}
	return NULL;
}


//二插树的高度
int TreeHeight(TNode* root) {
	if (root == NULL) {
		return 0;
	}
	int left_height = TreeHeight(root->left);
	int right_height = TreeHeight(root->right);
	return left_height > right_height ? left_height + 1 : right_height + 1;
}
//通过前序遍历创建二叉树
TNode* CreateTree(char* p, int* pi) {
    if (p[*pi] == '#') {

        return NULL;
        (*pi)++;
    }
	TNode* root = (TNode*)malloc(sizeof(TNode));
    if (!root) { // 检查内存分配是否成功
        fprintf(stderr, "Memory allocation failed\n");
        exit(EXIT_FAILURE);
    }
    root->data = p[(*pi)++];
    root->left = CreateTree(p, pi);
    root->right = CreateTree(p, pi);
    return root;
}

//销毁
void BinaryTreeDestory(TNode* root) {
	if (root == NULL)
	{
		return NULL;
	}
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

#include"Queue.h"
//层序遍历
void LevelOrder(TNode* root) {
	Queue s1;
	Q_Init(&s1);
	if (root)
	{
		Q_Push(&s1, root);
	}
	while (!Q_Empty(&s1))
	{
		TNode* Front = Q_Front(&s1);
		Q_Pop(&s1);
		printf("%d ", Front->data);
		if (Front->left)
			Q_Push(&s1, Front->left);
		if (Front->right)
			Q_Push(&s1, Front->right);

	}

	Q_Destroy(&s1);

}


// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(TNode* root) {
	Queue s1;
	Q_Init(&s1);
	if (root)
	{
		Q_Push(&s1, root);
	}
	while (!Q_Empty(&s1))
	{
		TNode* Front = Q_Front(&s1);
		Q_Pop(&s1);
		if (Front == NULL)
		{
			break;
		}
		Q_Push(&s1, Front->left);
		Q_Push(&s1, Front->right);
	}
	while (!Q_Empty(&s1))
	{
		TNode* Front_s = Q_Front(&s1);
		Q_Pop(&s1);
		if (Front_s != NULL) {
			return false;
		}
	}

	Q_Destroy(&s1);
	return true;
}


int main() {
	TNode* root = CreatBinaryTree();
	//PreOrder(root);
	//printf("\n");
	//InOrder(root);
	//printf("\n");
	//PostOrder(root);
	//printf("\n");
	//int ret_1 = BinaryTreeSize(root);
	//printf("树得节点个数:%d", ret_1);
	//printf("\n");
	//int ret_2 = BinaryTreeLeafSize(root);
	//printf("树的叶子节点的个数:%d",ret_2);
	//printf("\n");
	//printf("%d", BinaryTreeLevelKSize(root, 1));
	//printf("\n");
	//printf("%d", BinaryTreeLevelKSize(root, 2));
	//printf("\n");
	//printf("%d", BinaryTreeLevelKSize(root, 3));
	//printf("\n");
	//printf("高度:%d", TreeHeight(root));
	//printf("\n");
	//Type_Date x = 5; // 查找的值
	//TNode* result = BinaryTreeFind(root, x);
	//if (result != NULL) {
	//	printf("找到了\n");
	//}
	//else
	//{
	//	printf("没找到\n");
	//}
	LevelOrder(root);
}

分析图:

好了到了这里就二叉树就完结了

可以做做OJ来感受一下二叉树

二叉树—leetcode-CSDN博客文章浏览阅读68次。前言本篇博客我们来仔细说一下二叉树二叉树的一些OJ题目请看完上一篇:若有问题 评论区见📝。https://blog.csdn.net/2302_78381559/article/details/139564877?spm=1001.2014.3001.5501

  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

普通young man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值