数据结构c版(2)——二叉树

news2024/9/22 19:31:11

本章我们来了解一下二叉树这一概念。

目录

1.树概念及结构

1.1树的概念​​​​​​​

1.2 树的特点:

1.3 树的相关概念

1.4 树的表示​​​​​​​

1.5 树在实际中的运用(表示文件系统的目录树结构)

2.二叉树概念及结构

2.1概念

2.3 特殊的二叉树:

        1. 满二叉树:

        2. 完全二叉树:

2.4 二叉树的性质

2.5 二叉树的存储结构

1. 顺序存储

2. 链式存储

3.二叉树的顺序结构及实现

3.1 二叉树的顺序结构

3.2 堆的概念及结构

3.3 堆的实现

3.2.1 堆向下调整算法

3.2.2堆的创建

3.2.3 建堆时间复杂度​​​​​​​

3.2.4 堆中元素调整

3.2.5 堆的插入

3.2.6 堆中元素的判断

3.2.7 堆中元素的删除

3.2.8 堆的摧毁

3.2.9 堆的头文件

3.3 堆的应用

3.3.1 堆排序

3.3.2 TOP-K问题

4.二叉树链式结构的实现

4.1 前置说明

4.2二叉树的遍历

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

4.2.2 层序遍历

4.3 节点个数以及高度等

4.4 二叉树的创建和销毁

4.5 二叉树头文件


1.树概念及结构

1.1树的概念

树是一种非线性的数据结构,它是由n n>=0)个有限结点组成一个具有层次关系的集合。  把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的         

1.2 树的特点:

          (1) 所有树都有一个特殊的结点,称为根结点它是整个树的起点,其他节点都是从根节点开始向下延伸的。

          (2) 每个节点在树中都是唯一的,每个节点都可以通过其在树中的位置被唯一地标识。

          (3) 树形结构的节点之间呈现出层级关系,每个节点可以有多个子节点,但只能有一个父节点,除了根节点没有父节点。

          (4)在数据结构树中,每个节点都不能构成环,即不存在一个节点的子孙节点中存在其祖先节点的情况。

          (5) 树是递归定义的。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

1.3 树的相关概念

1.节点的度 :一个节点含有的子树的个数称为该节点的度;   如上图:  A的为6。

2.叶节点或终端节点:度为0的节点称为叶节点;   如上图:  BC H I...等节点为叶节点。

3.非终端节点或分支节点:度不为0的节点;   如上图:  D E FG...等节点为分支节点。

4.双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;   如上图:  AB的父节点。

5.孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点;   如上图:  BA的孩子节点。

6.兄弟节点:具有相同父节点的节点互称为兄弟节点;   如上图:  BC是兄弟节点。

7.树的度 :一棵树中,最大的节点的度称为树的度;   如上图:树的度6。

8.节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

9.树的高度或深度:树中节点的最大层次;   如上图:树的高度为4。

10.堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:  H I互为兄弟节点。

11.节点的祖先:从根到该节点所经分支上的所有节点;如上图:  A是所有节点的祖先。

12.子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。

13.森林:由 m m>0)棵互不相交的树的集合称为森林;

1.4 树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了, 既然保存值域,也要保存结点和结点之间 的关系 ,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法
typedef int DataType;
struct Node
{
     struct Node* _firstChild1; // 第一个孩子结点
     struct Node* _pNextBrother; // 指向其下一个兄弟结点
     DataType _data; // 结点中的数据域
};

1.5 树在实际中的运用(表示文件系统的目录树结构)

2.二叉树概念及结构

2.1概念

一棵二叉树是结点的一个有限集合,该集合 :
        1. 或者为空
        2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从上图可以看出:
        1. 二叉树不存在度大于2的结点。
        2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
注意:对于任意的二叉树都是由以下几种情况复合而成的

2.3 特殊的二叉树:

        1. 满二叉树

        一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为h,且结点总数是,则它就是满二叉树。

        2. 完全二叉树:

        完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为h 的,有N 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 至N 的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

2.4 二叉树的性质

1. 若规定根节点的层数为 1 ,则一棵非空二叉树的 i 层上最多有2^{(i-1)}个结点。
2. 若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是2^{h}-1
3. 对任何一棵二叉树 , 如果度为 0 其叶结点个数为 n_{0} , 度为 2 的分支结点个数为n_{1} ,则有
n_{0}n_{1}+1
4. 若规定根节点的层数为 1 ,具有 n 个结点的满二叉树的深度 h=log_{2}(n+1)(ps:log_{2}(n+1) 是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 否则无右孩子。

2.5 二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构

1. 顺序存储

        顺序结构存储就是使用数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。

2. 链式存储

        二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,本篇我们学习使用的是二叉链,后续我们会更加深入的进行学习三叉链等。

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
     struct BinTreeNode* _pLeft; // 指向当前节点左孩子
     struct BinTreeNode* _pRight; // 指向当前节点右孩子
     BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
     struct BinTreeNode* _pParent; // 指向当前节点的双亲
     struct BinTreeNode* _pLeft; // 指向当前节点左孩子
     struct BinTreeNode* _pRight; // 指向当前节点右孩子
     BTDataType _data; // 当前节点值域
};

3.二叉树的顺序结构及实现

3.1 二叉树的顺序结构

        普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆( 一种二叉树 ) 使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

3.2 堆的概念及结构

        如果有一个关键码的集合 K=\left \{ k_{0},k_{1},k_{2},..., k_{n-1} \right \} ,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: K_{i}<=K_{2*i+1} 且 K_{i}<=K_{2*i+2}K_{i}>=K_{2*i+1} 且 K_{i}>=K_{2*i+2} ) i = 0,1 ,2…,则称为小堆 ( 或大堆 ) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
        1.堆中某个节点的值总是不大于或不小于其父节点的值;
        2.堆总是一棵完全二叉树。

3.3 堆的实现

3.2.1 堆向下调整算法

        现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
int array[] = {27,15,19,18,28,34,65,49,25,37};

3.2.2堆的创建

        下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
int a[] = {1,5,3,8,7,6};

// 小堆
//堆初始化函数
void HeapInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

3.2.3 建堆时间复杂度

        因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明( 时间复杂度本来看的就是近似值,多几个节点不影响最终结果)
因此: 建堆的时间复杂度为 O(N)

3.2.4 堆中元素调整

为了便于插入元素的同时满足堆的特性,我们需要写一个调整堆中元素的函数:

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//向上调整法 
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
			//child = (child - 1) / 2;
			//parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//向下调整法
void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;

	while (child < size)
	{
		// 假设左孩子小,如果解设错了,更新一下

		if (child+1 < size && a[child + 1] < a[child])
		{
			++child;
		}

		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

​​​​​​​

3.2.5 堆的插入

先插入一个 10 到数组的尾上,再进行向上调整算法,直到满足堆。
// O(logN)
//堆的插入函数
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		
		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

3.2.6 堆中元素的判断

为了方便对堆中元素的状况的判断,我们还需要写一下这些函数:

//获取堆顶元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

//获取堆中元素的个数
size_t HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

//判断堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

3.2.7 堆中元素的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
//堆中元素的删除
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}

3.2.8 堆的摧毁

在堆使用完毕后,我们需要摧毁已经建立的队,释放系统资源,释放之前向系统申请的堆区域的空间。

//摧毁堆
void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

3.2.9 堆的头文件

#define _CRT_SECURE_NO_WARNINGS 
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

void HeapInit(HP* php);

void HeapDestroy(HP* php);

void HeapPush(HP* php, HPDataType x);
// 规定删除堆顶(根节点)
void HeapPop(HP* php);

HPDataType HeapTop(HP* php);
size_t HeapSize(HP* php);
bool HeapEmpty(HP* php);

void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int size, int parent);

3.3 堆的应用

3.3.1 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
        升序:建大堆
        降序:建小堆
2. 利用堆删除思想来进行排序
        建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序
例:
以下是一个堆排代码:
// 升序堆排
void HeapSort(int* a, int n)
{
	// 建大堆
	// O(N*logN)
	/*for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}*/

	// O(N)
	for (int i = (n-1-1)/2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

3.3.2 TOP-K问题

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等。
        对于Top-K 问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 ( 可能数据都不能一下子全部加载到内存中) 。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前 K 个元素来建堆
        前k 个最大的元素,则建小堆;
        前k 个最小的元素,则建大堆。
2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素。
        将剩余N-K 个元素依次与堆顶元素比完之后,堆中剩余的 K 个元素就是所求的前 K 个最小或者最大的元素。
以下为参考解决代码:
//创建文件存入10000000个随机数
void CreateNDate()
{
	// 造数据
	int n = 10000000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < n; ++i)
	{
		int x = (rand()+i) % 10000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

//堆排获取文件中最大的k个数
void PrintTopK(const char* file, int k)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 建一个k个数小堆
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc error");
		return;
	}

	// 读取前k个,建小堆
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
		AdjustUp(minheap, i);
	}

	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		if (x > minheap[0])
		{
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");

	free(minheap);
	fclose(fout);
}

4.二叉树链式结构的实现

4.1 前置说明

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

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->left = node5;
     node4->right = node6;
     return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
再看二叉树基本操作前,再回顾下二叉树的概念, 二叉树是:
        1. 空树
        2. 非空:根节点,根节点的左子树、根节点的右子树组成的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

4.2二叉树的遍历

4.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)
{
	if (root == NULL)
	{
		return;
	}
	
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}


// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	
	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}


// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}
下面主要分析前序递归遍历,中序与后序图解类似。
前序遍历递归图解
前序遍历结果: 1 2 3 4 5 6
中序遍历结果: 3 2 1 5 4 6
后序遍历结果: 3 2 5 6 4 1

4.2.2 层序遍历

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

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;   //这里创建了一个队列,借助队列先进先出的特性实现了层序
	QueueInit(&q);   //队列初始化
	if (root)
	{
		QueuePush(&q, root);  //元素入队列
	}
	int levelsize = 1;
	while (!QueueEmpty(&q))      //队列为空停止训话
	{
		while (levelsize--)       
		{
			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");
		levelsize = QueueSize(&q);    //获取队列元素个数
	}
	printf("\n");
	QueueDestroy(&q);                 //摧毁队列
	
}

4.3 节点个数以及高度等

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

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

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

		if (k == 1)
			return 1;

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

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

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
	Queue q;           //同样借助队列进行
	QueueInit(&q);
	QueuePush(&q, root);
	while(!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	//如果节点为空,但队列此时不为零
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			QueueDestroy(&q);
			return 0;
		}
		
	}
	QueueDestroy(&q);
	return 1;

}

4.4 二叉树的创建和销毁

//创建二叉树
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 BinaryTreeDestory(BTNode** root)
{
	if (*root)
	{
		BinaryTreeDestory(&(*root)->left);
		BinaryTreeDestory(&(*root)->right);
		free(*root);
        *root = NULL;
	}
}



4.5 二叉树头文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#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);

本章结束! 

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

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

相关文章

H3C接入交换机收到大量上行口(连接汇聚交换机)TC报文该怎么处理?(排查思路及解决办法)

一、问题描述 H3C接入交换机收到大量上行口的TC报文。 二、告警信息 三、TC报文排查思路 1、检查物理链路是否有问题。 2、检查是否有可能存在环路。 3、终端接入的端口开启STP边缘端口。 4、根桥的下联端口开启STP根保护。 5、如果对端设备不是管辖的范围且无法管理到,可考虑…

「优选算法刷题」:在每个树行中找最大值

一、题目 给定一棵二叉树的根节点 root &#xff0c;请找出该二叉树中每一层的最大值。 示例1&#xff1a; 输入: root [1,3,2,5,3,null,9] 输出: [1,3,9]示例2&#xff1a; 输入: root [1,2,3] 输出: [1,3]提示&#xff1a; 二叉树的节点个数的范围是 [0,104]-231 < N…

WebSocket介绍+3分钟时间使用WebSocket搭建属自己的聊天室

WebSocket 的由来 在 WebSocket 出现之前&#xff0c;我们想实现实时通信、变更推送、服务端消息推送功能&#xff0c;我们一般的方案是使用 Ajax 短轮询、长轮询两种方式&#xff1a;比如我们想实现一个服务端数据变更时&#xff0c;立即通知客户端功能&#xff0c;没有 WebS…

【探索AI】二十一 深度学习之第4周:循环神经网络(RNN)与长短时记忆(LSTM)

循环神经网络&#xff08;RNN&#xff09;与长短时记忆&#xff08;LSTM&#xff09; RNN的基本原理与结构LSTM的原理与实现序列建模与文本生成任务实践&#xff1a;使用RNN或LSTM进行文本分类或生成任务步骤 1: 数据准备步骤 2: 构建模型步骤 3: 定义损失函数和优化器步骤 4: …

数字电路三宝:锁存器、寄存器和触发器

在数字电路设计中&#xff0c;很多电子工程师经常会用到锁存器、寄存器和触发器&#xff0c;它们各自承担着不同的功能&#xff0c;但共同为数字电路的稳定性和高效性提供了坚强保障&#xff0c;下面将谈谈这三大元件&#xff0c;希望对小伙伴们有所帮助。 1、锁存器&#xff0…

HOOPS Communicator对3D大模型轻量化加载与渲染的4种解决方案

今天给大家介绍一些关于3D Web轻量化引擎HOOPS Commuicator的关键概念&#xff0c;这些概念可以帮您在HOOPS Communicator流缓存服务器之上更好地构建您自己的模型流服务器。如果您是有大型数据集&#xff0c;那么&#xff0c;使用流缓存服务器可以极大地帮助您最大限度地减少内…

EthSign联合创始人 POTTER LI 确认出席Hack .Summit() 香港区块链开发者大会!

thSign联合创始人 POTTER LI确认将出席由 Hack VC 主办&#xff0c;并由 AltLayer 和 Berachain 联合主办&#xff0c;与 SNZ 和数码港合作&#xff0c;由 Techub News 承办的Hack.Summit() 2024区块链开发者盛会。 Potter Li&#xff0c;南加州大学应有数学系&#xff0c;南加…

hook函数——useReducer

目录 1.useReducer定义2.useReducer用法3.useState和useReducer区别 1.useReducer定义 const [state, dispatch] useReducer(reducer, initialArg, init?) reducer&#xff1a;用于更新 state 的纯函数。参数为 state 和 action&#xff0c;返回值是更新后的 state。state …

excel统计分析——拉丁方设计

参考资料&#xff1a;生物统计学 拉丁方设计也是随机区组设计&#xff0c;是对随机区组设计的一种改进。它在行的方向和列的方向都可以看成区组&#xff0c;因此能实现双向误差的控制。在一般的试验设计中&#xff0c;拉丁方常被看作双区组设计&#xff0c;用于提高发现处理效应…

新《公司法》规定5年内完成注册资本实缴有哪些影响

2024年对很多企业可谓是一个洗牌的年份。随着新公司法的颁布&#xff0c;很多企业都忧心忡忡面临着各种挑战。其中新《公司法》规定5年内完成注册资本实缴就让很多企业老板睡不着觉。新《公司法》规定注册资本实缴制度将对市场和企业产生一系列影响。主要有以下这几方面&#x…

【Java项目介绍和界面搭建】拼图小游戏——键盘、鼠标事件

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

【javascript】快速入门javascript

本文前言及说明 适合学过一门语言有一定基础的人看。 省略最初学习编程时的各种编程重复的基础知识。 javascript简介 编程语言&#xff08;主前端&#xff09; 用途&#xff1a;主web前后端&#xff0c;游戏&#xff0c;干别人网站 优点&#xff1a;速度快&#xff0c;浏…

Python之Web开发初学者教程----卸载ubuntu系统

Python之Web开发初学者教程----卸载ubuntu系统 Windows 10自带了Subsytem for Linux (WSL)功能&#xff0c;可以让用户在Windows命令行环境下运行Linux命令。用户可以在Windows应用商店中下载和安装Ubuntu子系统&#xff0c;有时在使用过程中需要完全删除Ubuntu子系统以释放硬…

Go语言学习-实现一个workshop

Creating new Go packages 1、创建一个Go package&#xff0c;叫&#xff1a; MyLib • Let’s create a Go package called MyLib and use it in our program 2、在go_project文件夹下开启终端&#xff0c;输入指令创建go.mod文件。 go mod init go_project• Assuming our…

【HTML】HTML基础6.1(表格以及常见属性)

目录 表格介绍 表格标签 表格标签的常见属性 案例 知识点总结 表格介绍 在浏览器中&#xff0c;我们经常见到形如 这样的表格形式&#xff0c;一般来说&#xff0c;表格是为了让数据看起来更加清晰&#xff0c;增强数据的可读性 有的程序员也会用表格进行排版 表格标签 &…

(UE4升级UE5)Selected Level Actor节点升级到UE5

本问所用工具为&#xff1a; UE5 UE4 插件AssetDeveTool包含&#xff1a;快速选择功能自动化批量LOD功能自动化批量展UV功能自动化批量减面功能自动化批量修改查找替换材质功能批量重命名工具碰撞器修改工具资源整理工具支持4.26 - 5.3版本https://mbd.pub/o/bread/mbd-ZZubkp…

ControlNet作者新作LayerDiffusion,让SD直接生成生成透明图像,堪比商用抠图软件

ControlNet作者又出新工作&#xff0c;这次的工作LayerDiffusion它使得大规模预训练的Stable Diffusion能够生成透明图像。该方法允许生成单个透明图像或多个透明图层&#xff0c;效果堪比商业产品Adobe Stock。而且LayerDiffusion和ControlNet一样支持基于SD微调的模型。 &quo…

Flutter的线程模型

在Flutter框架中&#xff0c;Embedder层负责把Flutter嵌入到各个平台上去&#xff0c;其所做的主要工作包括线程设置、渲染Surface设置&#xff0c;以及插件等。因此&#xff0c; Embedder负责线程的创建和管理&#xff0c;并且提供Task Runner给Engine使用。Engine则是负责提供…

钉钉h5应用 环境报错Error: Do not support the current environment:notInDingTalk

钉钉h5应用 环境报错 Error: Do not support the current environment&#xff1a;notInDingTalk problem Error: Do not support the current environment&#xff1a;notInDingTalk reason 前端页面运行在普通浏览器 solution 需要将h5页面在后台发布后&#xff0c;在钉…

Java中的日期时间类详解(建议收藏)!!!

Java中的日期时间类详解 1. LocalDate、LocalTime和LocalDateTime2. DateTimeFormatter3. 日期时间计算和比较4. **时区和日历**&#xff1a; 总结 本文详细解释了Java提供了 java.time 包来处理日期和时间的方式。 1. LocalDate、LocalTime和LocalDateTime LocalDate &#…