数据结构:4_二叉树

news2024/12/23 18:17:33

二叉树

一.树概念及结构

1. 树的概念

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

  • 有一个**特殊的结点,称为根结点,**根节点没有前驱结点

  • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

  • 因此,树是递归定义的。
    在这里插入图片描述

  • 注意:树形结构中,子树之间不能有交集,否则就不是树形结构
    在这里插入图片描述

2. 树的相关概念

在这里插入图片描述

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;

3. 树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法

typedef int DataType;
struct Node
{
 struct Node* _firstChild1; // 第一个孩子结点
 struct Node* _pNextBrother; // 指向其下一个兄弟结点
 DataType _data; // 结点中的数据域
};

在这里插入图片描述

  • 改方法是最优方法
  • 初次之外还有两种方法表示树
    在这里插入图片描述

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

在这里插入图片描述

二.二叉树概念及结构

1. 概念

  • 一棵二叉树是结点的一个有限集合,该集合:

    • (1). 或者为空
    • (2). 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
      在这里插入图片描述
  • 从上图可以看出:

    • (1). 二叉树不存在度大于2的结点
    • (2). 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
  • 注意:对于任意的二叉树都是由以下几种情况复合而成的:
    在这里插入图片描述

2. 特殊的二叉树:

(1). 满二叉树:

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

(2). 完全二叉树:

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(即:前h-1层是满的,最后一层不一定满,但是从左到右必须连续)

在这里插入图片描述

3. 二叉树的性质

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

4.二叉树的存储结构

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

(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; // 当前节点值域
}

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

1. 二叉树的顺序结构

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

2. 堆的概念及结构

如果有一个关键码的集合K = {k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2*i+1且 Ki <= K2*i+2( Ki >= K2*i+1且Ki >= K2*i+2 ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

  • 堆的性质:

    • 堆中某个节点的值总是不大于或不小于其父节点的值;
    • 堆总是一棵完全二叉树
      在这里插入图片描述
  • 注意: 数据结构中的堆和C语言中的堆不一样

  • 堆 – 数据结构            ~~~~~~~~~~           完全二叉树

  • 堆 – c语言/操作系统    ~~   内存区域的划分

  • 不同学科里面的同名名称

1.下列关键字序列为堆的是:()
A 100,60,70,50,32,65
B 60,70,65,50,32,100
C 65,100,70,32,50,60
D 70,65,100,32,50,60
E 32,50,100,70,65,60
F 50,100,70,65,60,32
2.已知小根堆为8,15,10,21,34,16,12,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次
数是()。
A 1
B 2
C 3
D 4
3.一组记录排序码为(5 11 7 2 3 17),则利用堆排序方法建立的初始堆为
A(11 5 7 2 3 17)
B(11 5 7 2 17 3)
C(17 11 7 2 3 5)
D(17 11 7 5 3 2)
E(17 7 11 3 5 2)
F(17 7 11 3 2 5)
4.最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是()
A[3,2,5,7,4,6,8]
B[2,3,5,7,4,6,8]
C[2,3,4,5,7,8,6]
D[2,3,4,5,6,7,8]

1.A
2.C
3.C
4.C

3. 堆的实现

(1).堆向下调整算法 前提:左右子树必须是一个堆

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

在这里插入图片描述

(2).堆的创建 将以有数组直接变成堆

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 注意:一般建堆使用法2,其有两点优势:①时间复杂度小O(N)②只用写一个AdjustDown函数(堆排序后面一定要用)
(3).建堆时间复杂度
①向下调整建堆时间复杂度
  • 因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
    在这里插入图片描述
②向上调整建堆时间复杂度

在这里插入图片描述

(4).堆的插入 向上调整的前提是前面的树构成堆

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。
在这里插入图片描述

(5).堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
在这里插入图片描述

(6).堆的代码实现
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);
//堆的实现
typedef int HPDataType;

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


//小堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

void HeapDestory(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity =	php->size = 0;
}

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 (a[parent] > a[child])
//	{
//		Swap(&a[parent], &a[child]);
//		child = parent;
//		parent = (child - 1) / 2;
//	}
//}
//写法二
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	{
		if (a[child] < a[parent])//大小堆改此处:a[child] > a[parent]
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//O(logN)
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		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);
}


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])//大小堆改此处:a[child + 1] > a[child]
		{
			++child;
		}
		if (a[child] < a[parent])//大小堆改此处:a[child] > a[parent]
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

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);
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

bool HeapEmpty(HP* php)
{
	return php->size == 0;
}

4. 堆的应用

(1).堆排序–本质是一种选择排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

    1. 建堆
    • 升序:建大堆
    • 降序:建小堆
      在这里插入图片描述
    1. 利用堆删除思想来进行排序
      建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
      在这里插入图片描述

在这里插入图片描述

(2). TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

    1. 用数据集合中前K个元素来建堆
    • 前k个最大的元素,则建小堆
    • 前k个最小的元素,则建大堆
    1. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    • 将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
      在这里插入图片描述
void PrintTopK(int* a, int n, int k)
{
 // 1. 建堆--用a中前k个元素建堆
 
 // 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
}
void CreateNDate()
{
	//造数据
	int n = 10000000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		int x = (rand()+i) % n;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
//Topk问题
void PrintTopK(const char* file, int k)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}

	//建堆
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	//读取前k个数据,建小堆
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);//%d后不用写/n
		AdjustUp(minheap, i);
	}
	
	int x = 0;
	while (fscanf(fout, "%d", &x)!=EOF)//%d后不用写/n
	{
		if (x > minheap[0])
		{
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	free(minheap);
	fclose(fout);
}
int main()
{
	//CreateNDate();
	PrintTopK("data.txt", 5);
	return 0;
}

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

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 . 非空:根节点,根节点的左子树、根节点的右子树组成的。
      在这里插入图片描述
  • 从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

2. 二叉树的遍历

(1).前序、中序以及后序遍历

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

  • 按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
    • 1.前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。[根 左子树 右子树]
    • 2.中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。[左子树 根 右子树]
    • 3.后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。[左子树 右子树 根]

也叫做先根,中根,后根遍历

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

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

//中序访问
void InOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

//后序访问
void PostOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

在这里插入图片描述

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

前序遍历递归图解:
在这里插入图片描述

在这里插入图片描述

(2).层序遍历
void LevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	int levelSize = 1;
	while (!QueueEmpty(&q))
	{
		//levelSize是每一层的数据个数,控制一层一层出
		// 一层一层出
		while (levelSize--)
		{
			TreeNode* front = QueueFront(&q);
			QueuePop(&q);
			printf("%c ", front->data);//根据BTDataType选择打印格式

			if (front->left)
				QueuePush(&q, front->left);

			if (front->right)
				QueuePush(&q, front->right);
		}
		printf("\n");
		levelSize = QueueSize(&q);
	}
	printf("\n");

	QueueDestroy(&q);
}

3. 节点个数以及高度等

  • 注意:递归:
    • 1.子问题分治
    • 2.返回条件
// 1.二叉树节点个数
int TreeSize(TreeNode* root)
{
	return root == NULL ? 0 : 1 + TreeSize(root->left) + TreeSize(root->right);
}

// 2.二叉树叶子节点个数
int TreeLeafSize(TreeNode* root)
{
	//空 返回0
	if (root == NULL)
		return 0;
	//不是空,是叶子 返回1
	if (root->left==NULL && root->right==NULL)
		return 1;
	//不是空 也不是叶子 分治=左右子树叶子之和 
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

// 补充:树的高度
//法一
int TreeHeight(TreeNode* root)
{
	if (root == NULL)
		return 0;
	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//法二
int TreeHeight(TreeNode* root)
{
	if (root == NULL)
		return 0;
	return fmax(TreeHeight(root->left), TreeHeight(root->right))+1;
}

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

// 4.二叉树查找值为x的节点
TreeNode* TreeFind(TreeNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;

	TreeNode* left = TreeFind(root->left, x);
	if (left != NULL)
		return left;
	TreeNode* right = TreeFind(root->right, x);
	if (right != NULL)
		return right;

	return NULL;
}

4. 二叉树基础oj练习

1. 单值二叉树

https://leetcode.cn/problems/univalued-binary-tree/

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isUnivalTree(struct TreeNode* root) {
    if(root==NULL)
        return true;

    if(root->left!=NULL&&root->left->val!=root->val)
        return false;

    if(root->right!=NULL&&root->right->val!=root->val)
        return false;

    return isUnivalTree(root->left)&&isUnivalTree(root->right);
}
2. 相同的树

https://leetcode.cn/problems/same-tree/submissions/

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    //都为空
    if(p==q)
        return true;
    //一个为空一个不为空
    if((p==NULL&&q!=NULL)||(p!=NULL&&q==NULL))//或者直接写(p=NULL||q==NULL)
        return false;
    //都不为空
    if(p->val!=q->val)
        return false;
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}
3. 对称二叉树

https://leetcode.cn/problems/symmetric-tree/

//法一:利用相同二叉树代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    //都为空
    if(p==q)
        return true;
    //一个为空一个不为空
    if((p==NULL&&q!=NULL)||(p!=NULL&&q==NULL))//或者直接写(p=NULL||q==NULL)
        return false;
    //都不为空
    if(p->val!=q->val)
        return false;
    return isSameTree(p->left,q->right)&&isSameTree(p->right,q->left);
}

bool isSymmetric(struct TreeNode* root) {
    if(root==NULL)
        return true;
    return isSameTree(root->left,root->right);
}
//法二:新建子函数
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool _isSymmetric(struct TreeNode* root1,struct TreeNode* root2) {
    if(root1==root2)
        return true;
    if(root1==NULL||root2==NULL)
        return false;
    if(root1->val!=root2->val)
        return false;
    return _isSymmetric(root1->left,root2->right)&&_isSymmetric(root1->right,root2->left);
}

bool isSymmetric(struct TreeNode* root) {
    return _isSymmetric(root->left,root->right);
}
4. 二叉树的前序遍历

https://leetcode.cn/problems/binary-tree-preorder-traversal/description/

//法一:全局变量
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

int i = 0;

int TreeSize(struct TreeNode* root) {
    return root == NULL ? 0 : 1 + TreeSize(root->left) + TreeSize(root->right);
}

void preorder(struct TreeNode* root, int* a) {
    if (root == NULL)
        return;
    a[i++] = root->val;
    preorder(root->left, a);
    preorder(root->right, a);
}

// 输出型参数 (leetcode如果函数返回值是数组,不知道数组的大小。需要int* returnSize这样的输出型参数返回数组大小。)
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int n = TreeSize(root);
    int* a = (int*)malloc(sizeof(int) * n);
    *returnSize = n;

    i = 0;
    preorder(root, a);
    return a;
}
//法二:局部变量
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

int TreeSize(struct TreeNode* root) {
    return root == NULL ? 0 : 1 + TreeSize(root->left) + TreeSize(root->right);
}

void preorder(struct TreeNode* root, int* a, int* pi) {
    if (root == NULL)
        return;
    a[(*pi)++] = root->val;
    preorder(root->left, a, pi);
    preorder(root->right, a, pi);
}

// 输出型参数 (leetcode如果函数返回值是数组,不知道数组的大小。需要int*
// returnSize这样的输出型参数返回数组大小。)
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int n = TreeSize(root);
    int* a = (int*)malloc(sizeof(int) * n);
    *returnSize = n;

    int i = 0;
    preorder(root, a,&i);
    return a;
}
5. 二叉树的中序遍历

https://leetcode.cn/problems/binary-tree-inorder-traversal/

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int TreeSize(struct TreeNode* root)
{
    return root==NULL?0:1+TreeSize(root->left)+TreeSize(root->right);
}

void inorder(struct TreeNode* root,int* a,int* pi)
{
    if(root==NULL)
        return;
    inorder(root->left,a,pi);    
    a[(*pi)++]=root->val;
    inorder(root->right,a,pi);    
}
    
int* inorderTraversal(struct TreeNode* root, int* returnSize) {
    int n=TreeSize(root);
    *returnSize=n;
    int* a=(int*)malloc(sizeof(int)*n);
    int i=0;
    inorder(root,a,&i);
    return a;
}
6. 二叉树的后序遍历

https://leetcode.cn/problems/binary-tree-postorder-traversal/description/

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int TreeSize(struct TreeNode* root)
{
    return root==NULL?0:1+TreeSize(root->left)+TreeSize(root->right);
}

void postorder(struct TreeNode* root, int* a,int* pi)
{
    if(root==NULL)
        return;
    postorder(root->left,a,pi);
    postorder(root->right,a,pi);
    a[(*pi)++]=root->val;
}

int* postorderTraversal(struct TreeNode* root, int* returnSize) {
    int n=TreeSize(root);    
    *returnSize=n;
    int* a=(int*)malloc(sizeof(int)*n);
    int i=0;
    postorder(root,a,&i);
    return a;
}
7. 另一棵树的子树

https://leetcode.cn/problems/subtree-of-another-tree/description/

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    //都为空
    if(p==q)
        return true;
    //一个为空一个不为空
    if((p==NULL&&q!=NULL)||(p!=NULL&&q==NULL))//或者直接写(p=NULL||q==NULL)
        return false;
    //都不为空
    if(p->val!=q->val)
        return false;
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(isSameTree(root,subRoot))
        return true;
    if(root==NULL)
        return false;
    return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}

5. 二叉树的创建和销毁

二叉树的构建及遍历。
https://www.nowcoder.com/practice/4b91205483694f449f94c179883c1fef?tpId=60&&tqId=29483&rp=1&ru=/activity/oj&qru=/ta/tsing-kaoyan/question-ranking

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
TreeNode* TreeCreate(char* a, int* pi)
// 二叉树销毁
void DestoryTree(TreeNode* root)
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
// 1. 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
TreeNode* TreeCreate(char* a, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	if (root == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	root->data = a[(*pi)++];
	root->left = TreeCreate(a, pi);
	root->right = TreeCreate(a, pi);

	return root;
}
// 2. 二叉树销毁
void DestoryTree(TreeNode* root)//调用的人调用后自己把root置空
{
	if (root == NULL)
		return;
	DestoryTree(root->left);
	DestoryTree(root->right);
	free(root);
	return;
}
// 3. 是否是完全二叉树
bool TreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
			break;
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	// 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front!=NULL)
			return false;
	}
	QueueDestroy(&q);
	return true;
}

五.总结

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
#include<math.h>

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}TreeNode;


// 1.前序访问
void PrevOrder(TreeNode* root);
// 2.中序访问
void InOrder(TreeNode* root);
// 3.后序访问
void PostOrder(TreeNode* root);
// 4.层序遍历
void LevelOrder(TreeNode* root);
// 5.二叉树销毁
void DestoryTree(TreeNode* root);
// 6.是否是完全二叉树
bool TreeComplete(TreeNode* root);

// 1.节点个数
int TreeSize(TreeNode* root);
// 2.叶子节点的个数
int TreeLeafSize(TreeNode* root);
//补充:树的高度
int TreeHeight(TreeNode* root);
// 3.第k层结点个数
int TreeLevelK(TreeNode* root, int k);
// 4.查找值为x的结点
TreeNode* TreeFind(TreeNode* root, BTDataType x);

// 通过前序遍历的数组构建二叉树
TreeNode* TreeCreate(BTDataType* a, int* pi);

// 手撕二叉树
TreeNode* BuyTreeNode(int x);
TreeNode* CreateTree();
#include"BinaryTree.h"
#include"Queue.h"

// 1.前序访问
void PrevOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);//根据BTDataType选择打印格式
	PrevOrder(root->left);
	PrevOrder(root->right);
}
// 2.中序访问
void InOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);//根据BTDataType选择打印格式
	InOrder(root->right);
}
// 3.后序访问
void PostOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);//根据BTDataType选择打印格式
}

// 4.层序遍历
void LevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	int levelSize = 1;
	while (!QueueEmpty(&q))
	{
		//levelSize是每一层的数据个数,控制一层一层出
		// 一层一层出
		while (levelSize--)
		{
			TreeNode* front = QueueFront(&q);
			QueuePop(&q);
			printf("%d ", front->data);//根据BTDataType选择打印格式

			if (front->left)
				QueuePush(&q, front->left);

			if (front->right)
				QueuePush(&q, front->right);
		}
		printf("\n");
		levelSize = QueueSize(&q);
	}
	printf("\n");

	QueueDestroy(&q);
}


// 5.二叉树销毁
void DestoryTree(TreeNode* root)//调用的人调用后自己把root置空
{
	if (root == NULL)
		return;
	DestoryTree(root->left);
	DestoryTree(root->right);
	free(root);
	return;
}

// 6.是否是完全二叉树
bool TreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
			break;
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	// 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front!=NULL)
			return false;
	}
	QueueDestroy(&q);
	return true;
}

// 1.节点个数
int TreeSize(TreeNode* root)
{
	return root == NULL ? 0 : 1 + TreeSize(root->left) + TreeSize(root->right);
}

// 2.叶子节点的个数
int TreeLeafSize(TreeNode* root)
{
	//空 返回0
	if (root == NULL)
		return 0;
	//不是空,是叶子 返回1
	if (root->left == NULL && root->right == NULL)
		return 1;
	//不是空 也不是叶子 分治=左右子树叶子之和 
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

//补充:树的高度
//法一
//int TreeHeight(TreeNode* root)
//{
//	if (root == NULL)
//		return 0;
//	int leftHeight = TreeHeight(root->left);
//	int rightHeight = TreeHeight(root->right);
//	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
//}
//法二
int TreeHeight(TreeNode* root)
{
	if (root == NULL)
		return 0;
	return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
}

// 3.第k层结点个数
int TreeLevelK(TreeNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	else
	{
		return TreeLevelK(root->left, k - 1) + TreeLevelK(root->right, k - 1);
	}
}

// 4.查找值为x的结点
TreeNode* TreeFind(TreeNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;

	TreeNode* left = TreeFind(root->left, x);
	if (left != NULL)
		return left;
	TreeNode* right = TreeFind(root->right, x);
	if (right != NULL)
		return right;

	return NULL;
}

// 通过前序遍历的数组构建二叉树
TreeNode* TreeCreate(BTDataType* a, int* pi)
{
	if (a[*pi] == -1)//a[*pi] == '#'
	{
		(*pi)++;
		return NULL;
	}

	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	if (root == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	root->data = a[(*pi)++];
	root->left = TreeCreate(a, pi);
	root->right = TreeCreate(a, pi);

	return root;
}

// 手撕二叉树
TreeNode* BuyTreeNode(int x)
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);

	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

TreeNode* CreateTree()
{
	TreeNode* node1 = BuyTreeNode(1);
	TreeNode* node2 = BuyTreeNode(2);
	TreeNode* node3 = BuyTreeNode(3);
	TreeNode* node4 = BuyTreeNode(4);
	TreeNode* node5 = BuyTreeNode(5);
	TreeNode* node6 = BuyTreeNode(6);
	TreeNode* node7 = BuyTreeNode(7);

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

	return node1;
}

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

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

相关文章

让你的资金运动起来,金钱的聪明处理方式

一、教程描述 本套教程主要讲解了金融思维和财务思维&#xff0c;常见投资工具的实操技巧&#xff0c;资产配置方案的制定方法&#xff0c;等等&#xff0c;将会重构你现有的投资观念&#xff0c;提升你认知的宽度和深度&#xff0c;可以轻松读懂财经新闻&#xff0c;不仅学会…

单片机学习笔记---LED呼吸灯直流电机调速

目录 LED呼吸灯 直流电机调速 模型结构 波形 定时器初始化函数 中断函数 主程序 上一节讲了电机的工作原理&#xff0c;这一节开始代码演示&#xff01; 我们上一篇说Ton的时间长Toff时间短电机会快&#xff0c;Ton的时间短Toff时间长电机会慢 并且我们还要保证无论Ton和…

红队ATKCK|红日靶场Write-Up(附下载链接)

网络拓扑图 下载地址 在线下载&#xff1a; http://vulnstack.qiyuanxuetang.net/vuln/detail/2/ 百度网盘 链接&#xff1a;https://pan.baidu.com/s/1nlAZAuvni3EefAy1SGiA-Q?pwdh1e5 提取码&#xff1a;h1e5 环境搭建 通过上述图片&#xff0c;web服务器vm1既能用于外…

【网络安全】什么样的人适合学?该怎么学?

有很多想要转行网络安全或者选择网络安全专业的人在进行决定之前一定会有的问题&#xff1a; 什么样的人适合学习网络安全&#xff1f;我适不适合学习网络安全&#xff1f; 当然&#xff0c;产生这样的疑惑并不奇怪&#xff0c;毕竟网络安全这个专业在2017年才调整为国家一级…

【BUUCTF 加固题】Ezsql 速通

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

隐函数的求导【高数笔记】

1. 什么是隐函数&#xff1f; 2. 隐函数的做题步骤&#xff1f; 3. 隐函数中的复合函数求解法&#xff0c;与求导中复合函数求解法有什么不同&#xff1f; 4. 隐函数求导的过程中需要注意什么&#xff1f;

Day01 javaweb开发——tlias员工管理系统

任务介绍 完成部门管理和员工管理的增删改查功能 环境搭建 前端---->后端---->数据库 准备数据库表创建springboot工程&#xff08;web、mybatis、mysql驱动、lombok&#xff09;application.properties中引入mybatis配置信息&#xff0c;准备对应的实体类准备三层架…

Midjourney绘图欣赏系列(四)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子&#xff0c;它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同&#xff0c;Midjourney 是自筹资金且闭源的&#xff0c;因此确切了解其幕后内容尚不…

深入理解lambda表达式

深入理解ASP.NET Core中的中间件和Lambda表达式 var builder WebApplication.CreateBuilder(args); var app builder.Build(); app.Use(async (context, next) > { // Add code before request. await next(context);// Add code after request.}); 这段C#代码是用于设…

Mac M2芯片配置PHP环境

Mac M2芯片配置PHP环境 1. XAMPP2. PHPBrew(PHP版本管理)安装php7.4.33版本 3. 直接使用homebrew 安装php环境参考 1. XAMPP 官网地址 https://www.apachefriends.org/ 安装 安装完成 web server打开后&#xff0c;在打开localhost 成功&#xff01; 2. PHPBrew(PHP版本管…

node+vue3+mysql前后分离开发范式——实现视频文件上传并渲染

文章目录 ⭐前言⭐ 功能设计与实现💖 node上传文件写入file_map映射表💖 vue3前端上传文件回显⭐ 效果⭐结束⭐前言 大家好,我是yma16,本文分享关于 node+vue3+mysql前后分离开发范式——实现视频文件上传并渲染。 技术选型 前端:vite+vue3+antd 后端:node koa 数据库…

Python asyncio高性能异步编程 详解

目录 一、协程 1.1、greenlet实现协程 1.2、yield关键字 1.3、asyncio 1.4、async & await关键字 二、协程意义 三、异步编程 3.1、事件循环 3.2、快速上手 3.3、await 3.4、Task对象 3.5、asyncio.Future对象 3.5、concurrent.futures.Future对象 3.7、异步…

python+django+vue汽车票在线预订系统58ip7

本课题使用Python语言进行开发。基于web,代码层面的操作主要在PyCharm中进行&#xff0c;将系统所使用到的表以及数据存储到MySQL数据库中 使用说明 使用Navicat或者其它工具&#xff0c;在mysql中创建对应名称的数据库&#xff0c;并导入项目的sql文件&#xff1b; 使用PyChar…

软考 系统分析师系列知识点之信息系统战略规划方法(11)

接前一篇文章&#xff1a;软考 系统分析师系列知识点之信息系统战略规划方法&#xff08;10&#xff09; 所属章节&#xff1a; 第7章. 企业信息化战略与实施 第4节. 信息系统战略规划方法 7.4.7 价值链分析法 价值链分析&#xff08;Value Chain Analysis&#xff0c;VCA&am…

BulingBuling - 《研究巴菲特》 [ Buffettology ]

研究巴菲特 使沃伦-巴菲特成为世界上最著名的投资者的那些以前未曾解释过的技术 作者&#xff1a;玛丽-巴菲特 Buffettology The Previously Unexplained Techniques That Have Made Warren Buffett The Worlds Most Famous Investor By Mary Buffett 内容提要 《Buffetto…

php数据类型以及运算符

php数据类型以及运算符 1. php数据类型2. 使用举例3. 运算符 1. php数据类型 包括 String(字符串)、Integer(整型)、Float(浮点型)、Boolean(布尔型)、Array(数组)、Object(对象)、NULL(空值) 2. 使用举例 1.字符串 2.整型 3.浮点型 4.布尔型 5.数组 6.对象 7.null 3. 运算符…

AI:129-基于深度学习的极端天气事件预警

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

阿里云服务器服务费怎么计算的?详细报价解析

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

书生·浦语-模型评测opencompass

大预言模型评测 模型评测包括主管评测与客观评测 测试模型对提示词的敏感性&#xff0c;或通过提示词获得更准确地答案 主流评测框架 opencompass评测平台 作业

普中51单片机学习(一)

开发板功能和使用介绍 功能介绍 普中51-单核-A2开发板&#xff0c;采用单CPU设计&#xff0c;用的是STC公司生产的51内核芯片STC89C52&#xff0c;是一款拥有64KB FLASH超大存储器的51单片机。 五线四相步进电机驱动模块。使用ULN2003芯片&#xff0c;可驱动直流电机、五线…