数据结构入门——二叉树(C语言实现)

news2024/9/21 0:46:15

数据结构入门——二叉树

  • 一. 树概念及结构
    • 1.1 树的概念
    • 1.2 树的相关概念
    • 1.3 树的表示
    • 1.4 树的应用
  • 二. 二叉树概念及结构
    • 2.1 二叉树的概念
    • 2.2 特殊的二叉树
    • 2.3 二叉树的性质
    • 2.4 二叉树的存储结构
  • 三. 二叉树的顺序结构及其实现(堆的实现)
    • 3.1 二叉树的顺序结构
    • 3.2 堆的实现(以大堆为例)
      • 3.2.1 堆的结构声明
      • 3.2.2 堆的向下调整算法(建堆会用到)
      • 3.2.3 堆的向上调整算法
      • 3.2.4 建堆算法
      • 3.2.5 堆的完整代码
    • 3.3 堆的应用
  • 四. 二叉树链式结构的实现
    • 4.1 前置说明
    • 4.2 二叉树的遍历
      • 4.2.1 前序遍历
      • 4.2.2 中序遍历和后序遍历
      • 4.2.3 层序遍历
    • 4.3 二叉树的构建与销毁
      • 4.3.1 二叉树的构建
      • 4.3.2 二叉树的销毁
    • 4.4 二叉树的其他操作
      • 4.4.1 二叉树节点个数和叶子节点个数
      • 4.4.2 二叉树第k层节点个数
      • 4.4.3 二叉树查找值为x的节点
    • 4.5 完整代码
  • 五. 总结

一. 树概念及结构

1.1 树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树有一个特殊的结点,称为根结点,除根节点没有前驱结点除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继因此,树是递归定义的。
在这里插入图片描述
注意:树形结构中,子树之间不能有交集,否则就不是树形结构
在这里插入图片描述

1.2 树的相关概念

在这里插入图片描述

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

1.3 树的表示

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

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

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

1.4 树的应用

树比较常见的应用就是用目录的结构,比如Linux的树状目录结构;
在这里插入图片描述

二. 二叉树概念及结构

2.1 二叉树的概念

一棵二叉树是结点的一个有限集合,该集合为空或者由一个根节点加上两棵别称为左子树和右子树的二叉树组成
在这里插入图片描述
通过概念和图解我们可以发现:

  1. 二叉树不存在度大于2的结点
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

注意:对于任意的二叉树都是由以下几种情况复合而成的
在这里插入图片描述
现实生活中的二叉树图片:
在这里插入图片描述

2.2 特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^K -1,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

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

2.3 二叉树的性质

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

2.4 二叉树的存储结构

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

  1. 顺序存储
    顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
    在这里插入图片描述
  2. 链式存储
    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
	struct BinTreeNode* pLeft; // 指向当前节点左孩子
	struct BinTreeNode* pRight; // 指向当前节点右孩子
 	BTDataType data; // 当前节点值域
}

三. 二叉树的顺序结构及其实现(堆的实现)

3.1 二叉树的顺序结构

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

在这里插入图片描述
堆的性质:

  1. 堆中某个节点的值总是不大于或不小于其父节点的值;
  2. 堆总是一棵完全二叉树。

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

3.2 堆的实现(以大堆为例)

声明一下,堆的实现我们不会先讲解如何建堆,而是先讲解调整算法,因为建堆时会用到调整算法。
还有两条性质:

  1. 已知父节点下标parent,要找找到其左孩子child:child=parent*2+1;
  2. 已知孩子结点,找其父亲结点:parent=(child-1)/2;

3.2.1 堆的结构声明

//堆的结构声明
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	size_t size;//已经存储的数据个数
	size_t capacity;//容量
}Heap;

3.2.2 堆的向下调整算法(建堆会用到)

首先再次声明:堆的实现中,会多次交换两个数据,这里我们将其封装成一个函数:

//交换两个数据
void swap(HPDataType* e1, HPDataType* e2)
{
	HPDataType tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

向下调整的思路:

  1. 首先找到父节点parent的孩子结点child
  2. 找到左右孩子中值较大的那一个与parent比较,如果值大于parent则交换。
  3. 重复上述过程。

图解:
在这里插入图片描述
代码实现:

//从上往下调整
void AdjustDown(HPDataType* a, size_t size, size_t parent)
{
	size_t child = (parent * 2) + 1;//找到左孩子
	while (child < size)
	{
		if (child + 1 < size && a[child] < a[child + 1])//找两个孩子中大的那一个
		{
			child = child + 1;
		}
		if (a[parent] < a[child])//满足条件则交换
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = (parent * 2) + 1;
		}
		else
		{
			break;
		}
	}
}

3.2.3 堆的向上调整算法

思路:

  1. 先找到child的父亲节点
  2. 比较两者值的大小,如果满足条件则交换
  3. 重复该过程

图解:
在这里插入图片描述
代码实现:

//向上调整
void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;//找父亲结点
	while (child > 0)
	{

		if (a[parent] < a[child])//满足条件则交换
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

3.2.4 建堆算法

思路:

  1. 先将数组元素全部导入至堆中
  2. 从最后一个结点的父亲结点开始,依次向上调整,其中调整的算法是向下调整算法

现在假设有数组arr={2,7,26,25,19,17,1,90,3,36};
我们将其构建成大堆,请看图解
图解:
在这里插入图片描述
动图来自算法可视化网站VisuAlgo:https://visualgo.net/zh
注:本篇博客的可视化算法都是来自该网站。

代码实现:

void HeapCreate(Heap* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);//拷贝数组
	php->size = php->capacity = n;

	// 从下往上建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);//才用向下调整算法
	}
}

3.2.5 堆的完整代码

Heap.h

//头文件包含
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdbool.h>

//结构声明
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	size_t size;
	size_t capacity;
}Heap;

//函数声明
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
void HeapInit(Heap* php);
//堆的初始化
void HeapInit(Heap* php);

// 堆的销毁
void HeapDestory(Heap* php);

//堆的插入
void HeapPush(Heap* php, HPDataType n);

//堆的删除(删头)
void HeapPop(Heap* php);

//返回堆顶元素
HPDataType HeapTop(Heap* php);

//返回堆的数据个数
size_t HeapSize(Heap* php);

//判断堆是否为空
bool HeapEmpty(Heap* php);

Heap.c

#include "Heap.h"

// 堆的构建
void AdjustDown(HPDataType* php, size_t size, size_t parent);
void HeapCreate(Heap* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->size = php->capacity = n;

	// 从下往上建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);
	}
}
//堆的初始化
void HeapInit(Heap* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = php->size = 0;
}

// 堆的销毁
void HeapDestory(Heap* php)
{
	assert(php);
	free(php->a);
	php->capacity = php->size = 0;
}

//交换两个数据
void swap(HPDataType* e1, HPDataType* e2)
{
	HPDataType tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

//向上调整,保证堆的成立
void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{

		if (a[parent] < a[child])
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//堆的插入
void HeapPush(Heap* php, HPDataType n)
{
	assert(php);
	//如果堆为空或者满了就扩容
	if (php->capacity == php->size)
	{
		size_t newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = n;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}

//从上往下调整
void AdjustDown(HPDataType* a, size_t size, size_t parent)
{
	size_t child = (parent * 2) + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child = child + 1;
		}
		if (a[parent] < a[child])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = (parent * 2) + 1;
		}
		else
		{
			break;
		}
	}
}
//堆的删除(删头)
void HeapPop(Heap* 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(Heap* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}


//返回堆的数据个数
size_t HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}


//判断堆是否为空
bool HeapEmpty(Heap* php)
{
	assert(php);
	return HeapSize(php) == 0;
}

3.3 堆的应用

  1. 堆排序(之后我们讲排序算法时一起讲)
  2. TOP-K问题:求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
    比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
    对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
    数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:1. 用数据集合中前K个元素来建堆,求前k个最大的元素,则建小堆;若求前k个最小的元素,则建大堆。2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素;将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

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

4.1 前置说明

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

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
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 failed");
		exit(-1);
	}
	newNode->data = x;
	newNode->left = newNode->right = NULL;
	return newNode;
}
void test1()
{
	BTNode* root = BuyNode('A');
	BTNode* n2 = BuyNode('B');
	BTNode* n3 = BuyNode('C');
	BTNode* n4 = BuyNode('D');
	BTNode* n5 = BuyNode('E');
	BTNode* n6 = BuyNode('F');
	root->left = n2;
	root->right = n3;
	n2->left = n4;
	n2->right = n5;
	n3->right = n6;
}
int main()
{
	test1();
	return 0;
}

有了以上代码,我们就能构建出如下的一颗二叉树:
![在这里插入图片描述](https://img-blog.csdnimg.cn/6648f943dc294abfba48c15e0ce7ec10.png

接下来我们就将通过在这棵树来学习二叉树的遍历

4.2 二叉树的遍历

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

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

4.2.1 前序遍历

动画演示:
在这里插入图片描述
递归图
在这里插入图片描述

代码实现:

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");//不想打印NULL的话可以去掉
		return;
	}
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

运行效果:
在这里插入图片描述

4.2.2 中序遍历和后序遍历

中序遍历
动画演示:
在这里插入图片描述
代码实现:

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

运行效果:
在这里插入图片描述

后序遍历代码:

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

运行效果:
在这里插入图片描述

4.2.3 层序遍历

顾名思义,层序遍历就是一层一层的遍历。层序遍历需要用到队列这个数据结构,该结构在我之前的博客有讲到
思路如下:

  1. 如果根节点不为空则入队列,此时队列要么为空,要么有一个根节点
  2. 在队列不为空的条件下,打印队头数据,然后在将其出队并且将对头数据的左右子树入队
  3. 重复上述操作
    在这里插入图片描述
    代码实现:
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%c ", front->data);
		//出队并且将左右子树入队
		QueuePop(&q);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}

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

	QueueDestroy(&q);
}

运行效果:
在这里插入图片描述

4.3 二叉树的构建与销毁

4.3.1 二叉树的构建

通过前序遍历的数组"ABD##E#H##CF##"构建二叉树
这一部分需要注意的就是数组数据的存放顺序需要按照前序遍历的顺序来存放,其中#表示为空
思路和前序遍历差不多,只是把打印换成了创建二叉树而以,直接上代码

//通过前序遍历的数组"ABD##E#H##CF##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	assert(a);
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = BuyNode(a[*pi]);
	(*pi)++;
	root->left = BinaryTreeCreate(a, pi);
	root->right = BinaryTreeCreate(a, pi);
	return root;
}

这样就可以直接构建出如下二叉树:
在这里插入图片描述
至此,我们以后构建二叉树就可以按照这种方法构建了。

4.3.2 二叉树的销毁

二叉树的销毁需要采用后序遍历的顺序进行销毁

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

4.4 二叉树的其他操作

通过以上的学习,我们已经对二叉树的构建,销毁和遍历有了一定掌握了,接下来我们一起进行二叉树的其他操作。

4.4.1 二叉树节点个数和叶子节点个数

两者都需要用到递归的思想。
求节点个数的思路:
从根节点开始,我们要知道该二叉树的节点个数,我们就需要知道其左子树和右子树的节点个数然后加上根节点即可
按照这个逻辑递归。
图解:
在这里插入图片描述
代码实现:

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

	//方法2
	return root == NULL ? 0 :
		BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

这里提供了两种写法,一个思路清晰,一个代码简介。

求叶子节点个数:
思路基本一样,只是不需要额外算上根节点自己了。直接上代码:

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

	//方法2
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

同样也是提供了两种方法

4.4.2 二叉树第k层节点个数

同样也是递归思想,还是以我们之前构建的二叉树为例,要求第三层的节点个数,我们从根出发,就是求第三层的节点个数,相对于求根的左子树的第二层节点个数和根的右子树的第二层的节点个数…依次递归,即求k > 1 子树的k-1层结点个数相加。
图解:
在这里插入图片描述
在这里插入图片描述
如果觉得没有讲述清楚,我们换个方式理解:A的第3层,相对于B和C来说是第2层,而B和C的第2层,相对于D、E和F来说是第一层,而第一层的节点个数要么为0,要么为1
代码实现:

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

4.4.3 二叉树查找值为x的节点

思路很简单,遍历一遍树来查找就行了。这里我们以前序遍历为例,上代码:

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

4.5 完整代码

BinaryTree.h

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
#include "Queue.h"
//申请一个新结点
BTNode* BuyNode(BTDataType x);
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
//二叉树的深度
int TreeHeight(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);
#include "Queue.h"

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);

BinaryTree.c

#include "BinaryTree.h"
//申请一个新结点
BTNode* BuyNode(BTDataType x)
{
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	if (newNode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newNode->data = x;
	newNode->left = newNode->right = NULL;
	return newNode;
}
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	assert(a);
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = BuyNode(a[*pi]);
	(*pi)++;
	root->left = BinaryTreeCreate(a, pi);
	root->right = BinaryTreeCreate(a, pi);
	return root;
}
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
		return;
	BinaryTreeDestory(&(*root)->left);
	(*root)->left = NULL;
	BinaryTreeDestory(&(*root)->right);
	(*root)->right = NULL;
	free(*root);
	*root = NULL;
}
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	//方法1
	//if (root == NULL)
	//return 0;
	//int ret = 1;
	//ret += BinaryTreeSize(root->left);
	//ret += BinaryTreeSize(root->right);
	//return ret;

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

	//杭哥方法
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
//二叉树的深度
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	// k > 1 子树的k-1层结点个数相加
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* rleft = BinaryTreeFind(root->left, x);
	if (rleft)
		return rleft;
	BTNode* rright = BinaryTreeFind(root->right, x);
	if (rright)
		return rright;
	return NULL;
}
// 层序遍历

void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%c ", front->data);
		QueuePop(&q);

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

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

	QueueDestroy(&q);
}

五. 总结

二叉树的内容是比较多的,而且很多算法的思路都需要用到递归的思想,需要大家多花时间思考和画图分析。希望本篇博客对大家的数据结构有帮助。后面两期的博客我应该会讲一下几个比较常见的排序算法和Linux的入门。
还有一点注意的是:本篇博客用到的可视化视频是在VisuAlgo网站截取的,如果大家有需要也可以去该网站学习。
那我们下一期博客再见吧!

在这里插入图片描述

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

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

相关文章

要想宝宝吃得好,粮仓就要保护好,做好3点保护粮仓,防止皲裂

众所周知&#xff0c;母乳喂养的妈妈并不容易&#xff0c;因为母乳喂养也有很多“难题”&#xff0c;也会很痛&#xff0c;比如开奶痛、挂奶、堵奶等&#xff0c;疼痛的程度不亚于子宫收缩。还有一个&#xff0c;牛奶&#xff0c;牛奶。.头皲裂的时候&#xff0c;真的是含泪喂奶…

Python实现可视化大屏数据

参考网址如下&#xff1a; 【Python】全网最新最全Pyecharts可视化教程(三)&#xff1a;制作多个子图_51CTO博客_python数据可视化pyecharts使用pyecharts拖拉&#xff0c;拽&#xff0c;创建大屏展示 - 简书 (jianshu.com) 智慧大屏是如何实现数据可视化的&#xff1f; - 知…

调查问卷考试问卷创建生成工具助手小程序开发

调查问卷考试问卷创建生成工具助手小程序开发 问卷调查考试软件&#xff0c;可以自由让每一个用户自由发起调查问卷、考试问卷。发布的问卷允许控制问卷的搜集、回答等各个环节的设置&#xff0c;同时支持系统模板问卷&#xff0c;选用模板问卷后可以一键创建属于自己的问卷&a…

JVM基础知识总结

日常工作中接触到的jvm相关的知识&#xff0c;和jvm相关书籍中汇总总结一下jvm相关基础知识&#xff0c;作为对jvm的了解。 文章目录jvm运行时数据区域程序计数器java虚拟机栈堆heap非堆内存 nonheap方法区直接内存 Direct Memory类加载机制类的加载过程类加载器加载过程的详细…

阿里云网络解决方案架构师任江波:全球一张网,支撑游戏业务高效互联

2022 年 8 月 30 日&#xff0c;阿里云用户组&#xff08;AUG&#xff09;第 9 期活动在北京举办。活动现场&#xff0c;阿里云网络解决方案架构师任江波&#xff0c;向参会企业代表分享了全球一张网&#xff0c;支撑游戏业务高效互联。本文根据演讲内容整理而成。 在座的很多我…

Web3中文|年度回顾:2022年Web3的发展情况

老生常谈的话题都有一个共同点&#xff0c;那就是它总是包含着一些无趣但至关重要的真理。例如&#xff0c;众所皆知天空是蓝色的&#xff0c;所以大家并不会把它纳入日常讨论&#xff0c;但这并不代表它对物理学、生物学和其他学科而言没有价值。 当我们回望2022年的Web3领域…

docker 部署maven服务器,并将代码发布到maven服务器,并kie-server关联

书接上文 已经搭建好了kie-server的docker swarm集群. 没有搭建business-central是集群是因为这是个页面的可视化的开发环境 一来面向开发人员,不需要集群部署 二来他的数据是放在本地的git服务器上的. 所以每个node的数据不会统一 三来部署好了之后页面也打不开… 四来busines…

推荐系统入门学习(一)【小白入门系列】

推荐系统入门学习&#xff08;一&#xff09; 前言&#xff1a;本博客不会采取大量的难懂的语言来介绍推荐系统&#xff0c;只会用一些简单的方式来介绍推荐系统&#xff0c;祝学习愉快&#xff01; 1、推荐系统的概念 简单的说&#xff0c;推荐系统则是将产品推荐给用户的一…

判断点在多边形内部

一、问题描述已知点P(x,y)和多边形polygon&#xff0c;判断点P(x,y)是否在多边形内部。二、解决方案射线法以点P为端点&#xff0c;向左方作射线L&#xff0c;由于多边形是有界的&#xff0c;所以射线L的左端一定在多边形外部。考虑沿着L从无究远处开始自左向右移动&#xff0c…

Android 更改鼠标样式

定义风格 鼠标风格定义位置&#xff1a;\frameworks\base\core\res\res\values\styles.xml 系统定义了两套鼠标的风格&#xff0c;一套是默认&#xff0c;一套是放大的图标的风格&#xff0c;在下面增加自己的鼠标风格图标 <style name"BluePointer"><ite…

Springboot Controller接口默认自动填充 业务实体参数值

前言 今天看有小伙伴求救&#xff1a; 我还是一贯如此&#xff0c; 有人不明白&#xff0c;没玩过HandlerMethodArgumentResolver 。 那么很可能不止他一个人&#xff0c; 那么我就有必要出手。 不多说&#xff0c;开搞。 正文 快速模拟出这个使用场景 &#xff1a; 假如有好多…

Allegro174版本新功能介绍之动态铜皮Fast模式设置

Allegro174版本新功能介绍之动态铜皮Fast模式设置 Allegro升级到了174版本后,相比于172版本把动态铜皮的FAST模式优化的更为到位,据介绍,铜皮smooth的时间可以缩短非常多,尤其是针对于数据量非常大PCB,效率提升不少,但是在出生产文件的时候,铜皮还是必须Smooth,见下面o…

Odoo 16 企业版手册 - 库存管理之作业类型

作业类型 Odoo库存模块是有效管理所有类型库存操作的最佳选择。为公司的库存管理提供适当的支持对于完美地运营业务至关重要。当我们谈论Odoo是库存管理的完美解决方案时&#xff0c;您会好奇它提供的功能。在本节中&#xff0c;我们将使用专用库存模块详细介绍Odoo管理的库存操…

Vue(七) 生命周期和组件(一)

目录 1. 生命周期 1.1 引出生命周期 1.2 生命周期——挂载流程 1.3 生命周期——更新流程 1.4 生命周期——销毁流程 1.5 总结 2. 非单文件组件 2.1 对组建的理解 2.2 非单文件组件 2.3 组件的注意点 2.4 组件的嵌套 2.5 VueComponent构造函数 1. 生命周期 生命周期…

正版授权|FastStone Image Viewer 图像编辑转换浏览器软件,个人免费的图像浏览器、转换器和编辑器。

前言&#xff1a;快速、稳定、用户友好的图像浏览器、转换器和编辑器。它具有一系列不错的功能&#xff0c;包括图像查看、管理、比较、红眼去除、电子邮件、调整大小、裁剪、修饰和颜色调整。以下是软件页面。 FastStone Image Viewer 是一个快速、稳定、用户友好的图像浏览器…

软件安全测试需要考虑哪些问题?看看专业软件测评中心怎么说

互联网信息时代&#xff0c;软件产品为我们的生活和工作中带来了极大的便利。我们使用的软件都有包含个人资料以及各类信息等安全因素&#xff0c;因此软件的安全问题是用户最为关注的话题之一。可以有效保障软件产品安全的的关键手段便是软件安全测试&#xff0c;那么在进行软…

力扣sql入门篇(九)

力扣sql入门篇(九) 1 股票的资本损益 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # 每个用户的所有Sell的price值减去Buy的price值就可以了 SELECT e.stock_name,sum(e.price) capital_gain_loss FROM ( SELECT stock_name,operation,operation…

解决可见性的方案有哪几种,你都知道吗???

1. 前言 上次【保证原子性的几种方式&#xff0c;你都知道吗&#xff1f;&#xff1f;&#xff1f;】 经过一顿大杂烩后&#xff0c;列举了几种原子性的解决方案。这次我们继续上次的话题&#xff0c;我们来说说可见性的解决方案。废话不多说&#xff0c;让我们赶快开始吧。 2.…

【阶段二】Python数据分析Pandas工具使用10篇:探索性数据分析:数据的检验:正态性检验

本篇的思维导图: 探索性数据分析:数据的检验 数据检索,则是对数据深层次的探索或挖掘,用于验证数据是否服从某种假设,这部分内容将以数据的正态性检验、卡方检验和t检验为例,结合Python讲解具体的使用方法。 正态性检验 统计学中的很多模型或检验都需要…

java旅游日志博客系统旅行记录系统

简介 记录个人旅游动态日志的系统&#xff0c;也可以用来做博客系统&#xff0c;主页可以发布旅游日志&#xff0c;关注博主&#xff0c;给博主留言&#xff0c;管理评论&#xff0c;博文点赞&#xff0c;个人主页。 演示视频 https://www.bilibili.com/video/BV1rv41147W1/?…