数据结构入门————树(C语言/零基础/小白/新手+模拟实现+例题讲解)

news2025/2/27 6:47:51

目录

1. 树的概念及其结构

        1.1 树的概念:

        1.2 树的相关概念:

        1.3 树的表示方法:

​编辑

        1.4 树的应用:

2. 二叉树的概念及其结构        

        2.1 概念:

        2.2 特点:

        2.3 特殊二叉树:

        2.4 二叉树的性质:

3. 二叉树的顺序存储结构 

        3.1 二叉树的顺序存储结构

        3.2 堆的概念及其结构

        3.3 堆的实现

4. 二叉树的链式存储

        4.1 前序 ,中序 ,后序遍历

        4.2 层序遍历


1. 树的概念及其结构

        1.1 树的概念:

        树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:1.有且仅有一个特定的称为根(Root)的结点;2.当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2...,Tm,其中每一个集合本身又是一颗树,称为根的子树(Sub Tree),如下图所示。

注:

        1. 子树是不相交的。

        2. 除了根节点以外,每个节点有且只有一个父节点

        3. 一棵有N个节点的树有N-1条边。

        1.2 树的相关概念:

     a.结点的分类

        结点的度:结点拥有的子树的个数。例如,A的度为6。

        树的度:最大结点的度为树的度。例如,上图结点的度为6,。

        叶结点/终端结点:度为0的结点。例如,结点B。

       非终端结点/分支结点:度不为0的结点。例如,结点D,E....

    b.结点的关系

        双亲结点/父结点:若一个结点含有子结点,那么该结点就是父结点。例如,A是B的父结点。

        孩子结点/子结点:结点子树的根称为该结点的孩子(Child)。例如,B是A的孩子。

        兄弟结点:具有相同父结点的结点称为兄弟结点。例如,B和C是兄弟结点。

        堂兄弟结点:双亲在同一层的结点称为堂兄弟结点。例如,H和I是堂兄弟结点。

        结点的祖先:从根结点到该结点的分支上的所有结点,称为该结点的祖先。例如,A时所有结点的祖先

        子孙:以某结点为根的子树中的任意结点都是该结点的子孙,例如,所有结点都是A的子孙。

    c.树的相关概念

        结点的层次:从根结点开始定义,根结点为第1层,根结点的子结点为第2层,以此类推。

        树的高度或深度:树中结点的最大层次。上图,树的高度为4。

        森林:由m棵互不相交的树的集合称为森林。

        1.3 树的表示方法:

                这里我们简单了解一下比较常用的:孩子兄弟表示法。

typedef int DataType;
struct Node
{
 struct Node*  LeftChild;    // 指向左边第一个孩子节点
 struct Node*  RightBrather; // 指向右边的兄弟节点
 DataType _data; // 结点中的数据域
};

        1.4 树的应用:

                我们在Linux系统中(操作系统的一种),使用的目录结构就是树状结构。

2. 二叉树的概念及其结构        

        2.1 概念:

                二叉树(Binary Tree)是n(n>=0)个结点的有限集,该集合或者为空集(空二叉树),或者由一个根结点和两棵互不相交的,分别称为根结点的左子树和右子树的二叉树组成。

        2.2 特点:

                a. 二叉树不存在度大于2的结点。

                b. 二叉树有左子树右子树之分,是有顺序的,不能颠倒,即使只有一棵子树,也要区分左子树还是右子树。

        2.3 特殊二叉树:

                a. 满二叉树:一个二叉树,每一层的结点数都达到最大值,则这个二叉树称为满二叉树。也就是说,如果一个满二叉树一共有h层,那么它的结点个数为 2^h-1。

                b. 完全二叉树:前h-1层结点数都达到最大值,最后一层不一定是满的,但一定从左往右有序。满二叉树是一个特殊的完全二叉树。

        2.4 二叉树的性质:

                1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^( i - 1 ) 个节点。 

                2. 若规定根节点的层数为1,则深度为h的二叉树的最大节点数是 2^( h )  - 1 。

                3. 对任何一棵二叉树,度为0的叶子节点个数为n0,度为2的分支节点个数为n2,则有n0 = n2 + 1。

                4. 若贵定根节点的层数为1,具有n个节点的满二叉树的深度,h = log₂(n + 1)

                5. 对具有n个节点的完全二叉树,如果按照从上到下从左至右的数组顺序对所有节点开始编号,啧对于序号为i的节点有:

                a. 若 i > 0,i位置节点的双亲序号:(i - 1) / 2 ; i =0,i为根节点编号,无双亲节点。

                b. 若2 * i + 1 < n,左孩子序号: 2 * i + 1,若 2 * i + 1 >= n ,则无左孩子节点。

                c. 若2 * i + 2 < n,右孩子序号: 2 * i + 2,若 2 * i + 1 >= n,则无右孩子节点。 

1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( B
A 不存在这样的二叉树
B 200
C 198
D 199
解析:
        叶子节点:度为0的节点,n0 = n2 +1
2. 在具有 2n 个结点的完全二叉树中,叶子结点个数为( A
A n
B n+1
C n-1
D n/2
解析:
        完全二叉树的节点度分为3种情况:度=1 or 度 =2 or 度 = 0;
        2n = n0 + n1 + n2
        n0 = n2 + 1 ,且 n1 = 0 或 n1 =0
        2n0 + n1 = 2n    这里 n1只能为0 (偶数 + 奇数  !=  偶数)
        所以叶子节点个数 = n
4.一棵完全二叉树的节点数位为531个,那么这棵树的高度为( B
A 11
B 10
C 8
D 12
解析:
        满二叉树的节点个数 = 2*h -1
        完全二叉树中,叶子节点个数不确定,区间为:[ 2^( h - 1 ) + 1 , 2^( h ) -1 ]
        将选项代入,得出高度为10
5.在一颗度为3的树中,度为3的结点有2个,度为2的结点有1个,度为1的结点有2个,则叶子结点有(  C )个

A.4

B.5

C.6

D.7

解析:

        度为i的节点为ni,树的节点个数为n,则 n = n0 + n1 + n2 + n3;

        有n个节点的树的总边数为 : n-1 条。

        根据度的定义,总边数 与 度 的关系: n -1 = 0 * n0 + 1 * n1 + 2 * n2 + 3 * n3

        联立方程可得,n0 = n2 +2 *n3 +1 , n0 = 6

3. 二叉树的顺序存储结构 

        3.1 二叉树的顺序存储结构

                普通的二叉树不适合用顺序存储结构,会造成大量空间浪费,但完全二叉树适合用顺序存储结构,现实中我们常把堆(一种二叉树)使用顺序结构存储。

        3.2 堆的概念及其结构

        堆中某个节点的值总是不大于或不小于其父节点的值;

        堆总是一棵完全二叉树。

        3.3 堆的实现

//Heap.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity; 
}Heap;

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
#include "Heap.h"

void Swap(HPDataType* x1, HPDataType* x2)
{
	HPDataType x = *x1;
	*x1 = *x2;
	*x2 = x;
}

void AdjustDown(HPDataType* a, int n, int root)
{
	int parent = root;
	int child = parent*2+1;
	while (child < n)
	{
		// 选左右孩纸中大的一个
		if (child+1 < n 
			&& a[child+1] > a[child])
		{
			++child;
		}

		//如果孩子大于父亲,进行调整交换 
		if(a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent*2+1;
		}
		else
		{
			break;
		}
	}
}

void AdjustUp(HPDataType* a, int n, int child)
{
	int parent;
	assert(a);
	parent = (child-1)/2;
	//while (parent >= 0)
	while (child > 0)
	{
        //如果孩子大于父亲,进行交换
		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child-1)/2;
		}
		else
		{
			break;
		}
	}
}

void HeapInit(Heap* hp, HPDataType* a, int n)
{
	int i;
	assert(hp && a);
	hp->_a = (HPDataType*)malloc(sizeof(HPDataType)*n);
	hp->_size = n;
	hp->_capacity = n;

	for (i = 0; i < n; ++i)
	{
		hp->_a[i] = a[i];
	}

	// 建堆: 从最后一个非叶子节点开始进行调整
    // 最后一个非叶子节点,按照规则: (最后一个位置索引 - 1) / 2
    // 最后一个位置索引: n - 1
    // 故最后一个非叶子节点位置: (n - 2) / 2
	for(i = (n-2)/2; i >= 0; --i)
	{
		AdjustDown(hp->_a, hp->_size, i);
	}
}

void HeapDestory(Heap* hp)
{
	assert(hp);

	free(hp->_a);
	hp->_a = NULL;
	hp->_size = hp->_capacity = 0;
}

void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
    //检查容量
	if (hp->_size == hp->_capacity)
	{
		hp->_capacity *= 2;
		hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity);
	}
	//尾插
	hp->_a[hp->_size] = x;
	hp->_size++;
	//向上调整
	AdjustUp(hp->_a, hp->_size, hp->_size-1);
}

void HeapPop(Heap* hp)
{
	assert(hp);
    //交换
	Swap(&hp->_a[0], &hp->_a[hp->_size-1]);
	hp->_size--;
	//向下调整
	AdjustDown(hp->_a, hp->_size, 0);
}

HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->_a[0];
}

int HeapSize(Heap* hp)
{
	return hp->_size;
}

int HeapEmpty(Heap* hp)
{
	return hp->_size == 0 ? 0 : 1;
}

void HeapPrint(Heap* hp)
{
	int i;
	for (i = 0; i < hp->_size; ++i)
	{
		printf("%d ", hp->_a[i]);
	}
	printf("\n");
}

        

4. 二叉树的链式存储

        先来简单复习一下二叉树的概念,二叉树是:

        1. 空树

        2.非空:根节点,左子树,右子树组成

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

        4.1 前序 ,中序 ,后序遍历

                二叉树遍历是按照某种特定的规则,依次对二叉树的节点进行相应的操作,并且每个节点只操作1次。

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

 1. 前序遍历(Preorder Traversal):访问根节点的操作发生在遍历其左右子树之前。

 2. 后序遍历(Postorder Traversal):访问根节点发生在遍历其左右子树之后。

 3. 中序遍历(Inorder Traversal):访问根节点的操作发生在遍历其左右子树之间。

        4.2 层序遍历

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

                层序遍历中,我们使用队列来实现,但因为C语言的局限性,需要自己创建轮子,所以实现起来比较复杂,这里如果你对队列不太熟悉,可以参考下面这篇文章,帮助你更好的理解。

        

       数据结构入门————栈和队列(C语言/零基础/小白/新手+模拟实现+例题讲解)

//Tree.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
#include "BTree.h"
#include "queue.h" //参考之前的代码
#include "stack.h"

BTNode *BinaryTreeCreate(BTDataType * src, int n, int* pi)
{
	if (*pi >= n || src[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	BTNode * cur = (BTNode *)malloc(sizeof(BTNode));
	cur->_data = src[*pi];
	(*pi)++;

	cur->_left = BinaryTreeCreate(src, n, pi);
	cur->_right = BinaryTreeCreate(src, n, pi);

	return cur;
}

void BinaryTreePrevOrder(BTNode* root)
{
	if (root)
	{ 
		putchar(root->_data);
		BinaryTreePrevOrder(root->_left);
		BinaryTreePrevOrder(root->_right);
	}
}

void BinaryTreeInOrder(BTNode* root)
{
	if (root)
	{
		BinaryTreeInOrder(root->_left);
		putchar(root->_data);
		BinaryTreeInOrder(root->_right);
	}
}

void BinaryTreePostOrder(BTNode* root)
{
	if (root)
	{
		BinaryTreePostOrder(root->_left);
		BinaryTreePostOrder(root->_right);
		putchar(root->_data);
	}
}

void BinaryTreeDestory(BTNode** root)
{
	if (*root)
	{
		BinaryTreeDestory(&(*root)->_left);
		BinaryTreeDestory(&(*root)->_right);
		free(*root);
        *root = NULL;
	}
}

void BinaryTreeLevelOrder(BTNode* root)
{
	Queue qu;
	BTNode * cur;

	QueueInit(&qu);

	QueuePush(&qu, root);

	while (!QueueIsEmpty(&qu))
	{
		cur = QueueTop(&qu);

		putchar(cur->_data);

		if (cur->_left)
		{
			QueuePush(&qu, cur->_left);
		}

		if (cur->_right)
		{
			QueuePush(&qu, cur->_right);
		}

		QueuePop(&qu);
	}

	QueueDestory(&qu);
}

int BinaryTreeComplete(BTNode* root)
{
	Queue qu;
	BTNode * cur;
	int tag = 0;

	QueueInit(&qu);

	QueuePush(&qu, root);

	while (!QueueIsEmpty(&qu))
	{
		cur = QueueTop(&qu);

		putchar(cur->_data);

		if (cur->_right && !cur->_left)
		{
			return 0;
		}

		if (tag && (cur->_right || cur->_left))
		{
			return 0;
		}

		if (cur->_left)
		{
			QueuePush(&qu, cur->_left);
		}

		if (cur->_right)
		{
			QueuePush(&qu, cur->_right);
		}
		else
		{
			tag = 1;
		}

		QueuePop(&qu);
	}

	QueueDestory(&qu);
	return 1;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

shell编程-awk命令详解(超详细)

文章目录 前言一、awk命令介绍1. awk命令简介2. awk命令的基本语法3. 常用的awk命令选项4. 常用的awk内置变量 二、awk命令示例用法1. 打印整行2. 打印特定字段3. 根据条件筛选行4. 自定义分隔符5. 从文件中读取awk脚本 总结 前言 awk命令是一种强大的文本处理工具&#xff0c…

Qt OpenCV 学习(一):环境搭建

对应版本 Qt 5.15.2OpenCV 3.4.9MinGW 8.1.0 32-bit 1. OpenCV 下载 确保安装 Qt 时勾选了 MinGW 编译器 本文使用 MinGW 编译好的 OpenCV 库&#xff0c;无需自行编译 确保下载的 MinGW 和上述安装 Qt 时勾选的 MinGW 编译器位数一致&#xff0c;此处均为 x86/32-bit下载地址…

LongAddr

目录 1. 引言 2. AtomicInteger的局限性 3. AtomicInteger与LongAdder 的性能差异 4.LongAdder 的结构 LongAddr架构 Striped64中重要的属性 Striped64中一些变量或者方法的定义 Cell类 5. 分散热点的原理 具体流程图 6. 在实际项目中的应用 7. 总结 1. 引言 在这一…

【C++练级之路】【Lv.2】类和对象(上)(类的定义,访问限定符,类的作用域,类的实例化,类的对象大小,this指针)

目录 一、面向过程和面向对象初步认识二、类的引入三、类的定义四、类的访问限定符及封装4.1 访问限定符4.2 封装 五、类的作用域六、类的实例化七、类的对象大小的计算7.1 类对象的存储方式猜测7.2 如何计算类对象的大小 八、类成员函数的this指针8.1 this指针的引出8.2 this指…

课题学习(十四)----三轴加速度计+三轴陀螺仪传感器-ICM20602

本篇博客对ICM20602芯片进行学习&#xff0c;目的是后续设计一个电路板&#xff0c;采集ICM20602的数据&#xff0c;通过这些数据对各种姿态解算的方法进行仿真学习。 一、 ICM20602介绍 1.1 初识芯片 3轴陀螺仪&#xff1a;可编程全刻度范围(FSR)为250 dps&#xff0c;500 d…

Proteus仿真--基于ADC0832设计的两路电压表

本文介绍基于ADC0832实现的双路电压表采集设计&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真图如下 采集芯片选用ADC0832&#xff0c;电压显示在LCD1602液晶显示屏上 仿真运行视频 Proteus仿真--基于ADC0832设计的两路电压表 附完整Proteus仿真资料代码资料…

自动驾驶学习笔记(十四)——感知算法

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo Beta宣讲和线下沙龙》免费报名—>传送门 文章目录 前言 感知算法 开发过程 测试和评价 前言…

【Linux】cp 命令使用

cp 命令 cp&#xff08;英文全拼&#xff1a;copy file&#xff09;命令主要用于复制文件或目录。 著者 由Torbjorn Granlund、David MacKenzie和Jim Meyering撰写。 语法 cp [选项]... [-T] 源文件 目标文件或&#xff1a;cp [选项]... 源文件... 目录或&#xff1a;cp [选…

【Docker】容器数据持久化及容器互联

一、Docker容器的数据管理 1.1、什么是数据卷 数据卷是经过特殊设计的目录&#xff0c;可以绕过联合文件系统&#xff08;UFS&#xff09;&#xff0c;为一个或者多个容器提供访问&#xff0c;数据卷设计的目的&#xff0c;在于数据的永久存储&#xff0c;它完全独立于容器的…

深入理解GMP模型

1、GMP模型的设计思想 1&#xff09;、GMP模型 GMP分别代表&#xff1a; G&#xff1a;goroutine&#xff0c;Go协程&#xff0c;是参与调度与执行的最小单位M&#xff1a;machine&#xff0c;系统级线程P&#xff1a;processor&#xff0c;包含了运行goroutine的资源&#…

canvas绘制单个圆和同心圆

代码实现&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdev…

CCKS2023-面向上市公司主营业务的实体链接评测-亚军方案

赛题分析 大赛地址 https://tianchi.aliyun.com/competition/entrance/532097/information 任务描述 本次任务主要针对上市公司的主营业务进行产品实体链接。需要获得主营业务中的产品实体&#xff0c;将该实体链接到产品数据库中的某一个标准产品实体。产品数据库将发布在竞赛…

HCIP —— 双点重发布 + 路由策略 实验

目录 实验拓扑&#xff1a; 实验要求&#xff1a; 实验配置&#xff1a; 1.配置IP地址 2.配置动态路由协议 —— RIP 、 OSPF R1 RIP R4 OSPF R2 配置RIP、OSPF 双向重发布 R3配置RIP、OSPF 双向重发布 3.查询路由表学习情况 4.使用路由策略控制选路 R2 R3 5.检…

[二分查找双指针]LeetCode881: 救生艇

作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 本文涉及的基础知识点 二分查找算法合集 题目 给定数组 people 。people[i]表示第 i 个人的体重 &#xff0c;船的数量不限&#xff0c;每艘船可以承载的最大重量为 limit。 每艘船最多可同时载两人&#xff0…

TZOJ 1431 整数的尾数

答案&#xff1a; #include<stdio.h> int main() {int a 0, b 0, n 0;int i 0, j 0;while (scanf("%d %d", &a, &b) ! EOF){int arr[100] { 0 };int count 0;if (a 0 && b 0) //如果ab都等于0{break; //跳出循环&#xff0c;停止…

Leetcode刷题详解——乘积为正数的最长子数组长度

1. 题目链接&#xff1a;1567. 乘积为正数的最长子数组长度 2. 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;请你求出乘积为正数的最长子数组的长度。 一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。 请你返回乘积为正数的最长子数组长度。 示…

上门按摩APP小程序,抓住机遇创新服务新模式;

上门按摩APP小程序&#xff1a;抓住机遇&#xff0c;创新服务新模式&#xff1b; 随着现代人对生活质量要求的提高&#xff0c;上门按摩服务正成为一种新的、受欢迎的生活方式。通过APP小程序&#xff0c;用户可以轻松预约按摩服务&#xff0c;解决身体疲劳问题&#xff0c;享受…

重启路由器可以解决N多问题?

为什么重启始终是路由器问题的首要解决方案? 在日常的工作学习工作中,不起眼的路由器是一种相对简单的设备,但这仍然是我们谈论的计算机。 这种廉价的塑料外壳装有 CPU、随机存取存储器 (RAM)、只读存储器 (ROM) 和许多其他组件。 该硬件运行预装的软件(或固件)来管理连接…

简单3D姿态基线模型网络架构与验证【SIM】

在这篇文章中&#xff0c;我们将回顾 ICCV’17 上提出的 Simple 3D Pose Baseline &#xff0c;即用于 3d 人体姿势估计的简单而有效的基线&#xff0c;也称为 SIM。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在…

Ant Design Pro 框架设置API Token拦截器的功能

分享记录一个解决方法&#xff0c;希望对大家有帮助。 找到文件&#xff0c;然后定义一个方法。最后调用一下即可。 代码我也给你贴上了。 // 获取token 拦截方法 const setTokenRequest (config: any) > {const token 30|eh5GNXWRe5rO4XLjbbnqy132RABfiKqI338EoIhqc790a…