数据结构--二叉树

目录

  • 1.树概念及结构
    • 1.1数的概念
    • 1.2数的表示
  • 2.二叉树概念及结构
    • 2.1二叉树的概念
    • 2.2数据结构中的二叉树
    • 2.3特殊的二叉树
    • 2.4二叉树的存储结构
      • 2.4.1顺序存储
      • 2.4.2链式存储
    • 2.5二叉树的性质
  • 3.堆的概念及结构
    • 3.1堆的实现
      • 3.1.1堆的创建
      • 3.1.2堆的插入
      • 3.1.3堆顶的删除
      • 3.1.4堆的代码实现
      • 3.1.5建堆时间复杂度
    • 3.2堆的应用
      • 3.2.1堆排序
      • 3.2.2TOP-K问题
  • 4.二叉树链式结构的实现
    • 4.1二叉树的定义
    • 4.2二叉树的遍历
      • 4.2.1二叉树的前序递归遍历
      • 4.2.2二叉树的中序递归遍历
      • 4.2.3二叉树的后序递归遍历
      • 4.2.4二叉树的层序遍历
    • 4.3二叉树节点数
    • 4.4二叉树叶子节点数
    • 4.5二叉树第K层节点数
    • 4.6二叉树查找值为X的节点
    • 4.7二叉树的深度
    • 4.8判断二叉树是否是完全二叉树
    • 4.9二叉树完整代码

1.树概念及结构

1.1数的概念

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

  1. 根结点:根节点没有前驱结点。
  2. 除根节点外,其余结点被分成是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。
  3. 因此,树是递归定义的。
    在这里插入图片描述

• 节点的度:一个节点含有的子树的个数称为该节点的度;如上图:A的为2

• 叶节点:度为0的节点称为叶子节点;如上图:D、E、F节点为叶子节点

• 非终端节点或分支节点:度不为0的节点; 如上图:B、C为分支节点

• 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点

• 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点

• 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点

• 树的度:一棵树中,最大的节点的度称为树的度;如上图:树的度为2

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

• 树的高度或深度:树中节点的最大层次;如上图:树的高度为3

• 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:B、F互为堂兄弟节点

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

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

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

1.2数的表示

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

typedef int DataType;
struct Node
{
     struct Node* firstChild1; 
     struct Node* pNextBrother; 
     DataType data; 
};

在这里插入图片描述

2.二叉树概念及结构

2.1二叉树的概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:

  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。

2.2数据结构中的二叉树

在这里插入图片描述

2.3特殊的二叉树

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

2.4二叉树的存储结构

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

2.4.1顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
在这里插入图片描述

2.4.2链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前介绍的是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。

   // 二叉链
   struct BinaryTreeNode
   {
    struct BinTreeNode* _pLeft; // 指向当前节点左孩子
    struct BinTreeNode* _pRight; // 指向当前节点右孩子
    BTDataType _data; // 当前节点值域
   };

在这里插入图片描述

2.5二叉树的性质

  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). (ps:Log2(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否则无右孩子

3.堆的概念及结构

在上面2.4.1中提到了堆,那么什么是堆呢?
堆从分类来分,分为大堆和小堆。从性质来看,堆其实是一个特别的完全二叉树,这个特别的完全二叉树中所有的父亲节点的值要么都大于等于子节点值(大堆),要么小于等于子节点值(小堆)。
在这里插入图片描述
堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;

2.堆总是一棵完全二叉树。

3.1堆的实现

3.1.1堆的创建

首先先定义一个堆
.h文件

#include<assert.h>
#include<stdbool.h>
#include<time.h>
typedef int HeapDateType;
typedef struct Heap
{
	HeapDateType* arr;
	int size;
	int capacity;
}HP;
//初始化
void HeapInit(HP* hp);
//销毁
void HeapDestory(HP* hp);

.c文件

#include"Heap.h"
//初始化
void HeapInit(HP* hp)
{
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}
//销毁
void HeapDestory(HP* hp)
{
	assert(hp);
	free(hp->arr);
	hp->capacity = hp->size = 0;
}

3.1.2堆的插入

堆的插入思路:
在这里插入图片描述
堆的插入代码:
.h文件

//插入数据
void HeapPush(HP* hp, HeapDateType x);
//向上调整
void AjustUp(HeapDateType* arr, int child);

.c文件

//向上调整
void AjustUp(HeapDateType* arr, int child)
{
	assert(arr);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//大于号是大堆,小于号是小堆
		if (arr[child] > arr[parent])
		{
			Swap(&arr[parent], &arr[child]);

			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//插入数据
void HeapPush(HP* hp, HeapDateType x)
{
	//检查一下是否需要扩容
	if (hp->capacity == hp->size)
	{
		hp->capacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
	}
	HeapDateType* ptr = (HeapDateType*)realloc(hp->arr, hp->capacity * sizeof(HeapDateType));
	if (ptr == NULL)
	{
		perror("HeapPushBack::realloc");
	}
	hp->arr = ptr;
	hp->arr[hp->size++] = x;
	AjustUp(hp->arr, hp->size - 1);
}

3.1.3堆顶的删除

堆顶的删除思路:
在这里插入图片描述
堆顶删除的代码:
.h文件

//删除堆顶数据
void HeapPop(HP* hp);
//向下调整
void AjustDown(HeapDateType* arr, int len, int parent);

.c文件

//向下调整
void AjustDown(HeapDateType* arr, int len, int parent)
{
	assert(arr);
	int child = parent * 2 + 1;

	while (child < len)
	{
		//大于号是大堆,小于号是小堆
		if (child + 1 < len && arr[child + 1] > arr[child])
		{
			child++;
		}

		//大于号是大堆,小于号是小堆
		if (arr[child] > arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//删除堆顶数据
void HeapPop(HP* hp)
{
	assert(hp);
	assert(!HeapIsEmpty(hp));

	//交换
	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
	hp->size--;

	//向下调整
	AjustDown(hp->arr, hp->size, 0);

}

3.1.4堆的代码实现

.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>
typedef int HeapDateType;
typedef struct Heap
{
	HeapDateType* arr;
	int size;
	int capacity;
}HP;
//初始化
void HeapInit(HP* hp);
//销毁
void HeapDestory(HP* hp);
//插入数据
void HeapPush(HP* hp, HeapDateType x);
//删除堆顶数据
void HeapPop(HP* hp);
//打印
void HeapPrint(HP* hp);
//向上调整
void AjustUp(HeapDateType* arr, int child);
//向下调整
void AjustDown(HeapDateType* arr, int len, int parent);
//断空
bool HeapIsEmpty(HP* hp);
//堆大小
int HeapSize(HP* hp);
//堆顶
HeapDateType HeapTop(HP* hp);
//交换
void Swap(HeapDateType* px, HeapDateType* py);

.c文件

#include"Heap.h"
//交换
void Swap(HeapDateType* px, HeapDateType* py)
{
	HeapDateType tmp = *px;
	*px = *py;
	*py = tmp;
}

//初始化
void HeapInit(HP* hp)
{
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

//销毁
void HeapDestory(HP* hp)
{
	assert(hp);
	free(hp->arr);
	hp->capacity = hp->size = 0;
}

//向上调整
void AjustUp(HeapDateType* arr, int child)
{
	assert(arr);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//大于号是大堆,小于号是小堆
		if (arr[child] > arr[parent])
		{
			Swap(&arr[parent], &arr[child]);

			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}

	}
}

//向下调整
void AjustDown(HeapDateType* arr, int len, int parent)
{
	assert(arr);
	int child = parent * 2 + 1;

	while (child < len)
	{
		//大于号是大堆,小于号是小堆
		if (child + 1 < len && arr[child + 1] > arr[child])
		{
			child++;
		}

		//大于号是大堆,小于号是小堆
		if (arr[child] > arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

//插入数据
void HeapPush(HP* hp, HeapDateType x)
{
	//检查一下是否需要扩容
	if (hp->capacity == hp->size)
	{
		hp->capacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
	}
	HeapDateType* ptr = (HeapDateType*)realloc(hp->arr, hp->capacity * sizeof(HeapDateType));
	if (ptr == NULL)
	{
		perror("HeapPushBack::realloc");
	}
	hp->arr = ptr;
	hp->arr[hp->size++] = x;
	AjustUp(hp->arr, hp->size - 1);

}

//删除堆顶数据
void HeapPop(HP* hp)
{
	assert(hp);
	assert(!HeapIsEmpty(hp));

	//交换
	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
	hp->size--;

	//向下调整
	AjustDown(hp->arr, hp->size, 0);

}

//断空
bool HeapIsEmpty(HP* hp)
{
	return hp->size == 0;
}

//堆大小
int HeapSize(HP* hp)
{
	return hp->size;
}

//堆顶
HeapDateType HeapTop(HP* hp)
{
	assert(!HeapIsEmpty(hp));
	return hp->arr[0];
}

//打印
void HeapPrint(HP* hp)
{
	for (int i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->arr[i]);
	}
	printf("\n");
}

3.1.5建堆时间复杂度

说到时间复杂度,就要考虑到最坏情况。那么假设完全二叉树的每一个节点都需要调整需要多少时间复杂度呢?
因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
在这里插入图片描述

3.2堆的应用

3.2.1堆排序

堆排序的思路:
在这里插入图片描述
建堆代码(方法1):

//构建大堆
//方法1
for (int i = 1; i < n; i++)
{
	AjustUp(a, i);
}

建堆代码(方法2):

//方法2
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	AjustDown(a, n, i);
}

堆排序代码:

void HeapSort(int* a, int n)
{
	assert(a);
	//构建大堆
	//方法1
	//for (int i = 1; i < n; i++)
	//{
	//	AjustUp(a, i);
	//}	//方法2
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AjustDown(a, n, i);
	}
	for (int end = n - 1; end > 0; end--)
	{
		Swap(&a[end], &a[0]);
		AjustDown(a, end, 0);
	}
}

3.2.2TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

  1. 用数据集合中前K个元素来建堆
    ·前k个最大的元素,则建小堆
    ·前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
    TOP-K代码:
//在n个数中找出最大的前K个
void PrintTopK(int* arr, int n, int k)
{
	HP hp;
	HeapInit(&hp);
	//先建立一个小堆
	for (int i = 0; i < k; i++)
	{
		HeapPush(&hp, arr[i]);
	}

	for (int i = k; i < n; i++)
	{
		if (arr[i] > hp.arr[0])
		{
			HeapPop(&hp);
			HeapPush(&hp, arr[i]);
		}
	}

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

void test02()
{
	int arr[10000] = { 0 };
	for (int i = 0; i < 10000; i++)
	{
		int t = rand() % 10000;
		arr[i] = t;
	}
	arr[131] = 10001;
	arr[141] = 10002;
	arr[151] = 10003;
	arr[161] = 10004;
	arr[171] = 10005;
	arr[181] = 10006;
	arr[191] = 10007;
	arr[1311] = 10008;
	arr[885] = 10009;
	arr[240] = 10010;
	PrintTopK(arr, 10000, 10);
}

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

4.1二叉树的定义

typedef char BTDataType;
typedef struct BinaryTree
{
	struct BinaryTree* left;
	struct BinaryTree* right;
	BTDataType val;
}BTNode;

4.2二叉树的遍历

4.2.1二叉树的前序递归遍历

前序遍历可以想象为,一个小人从一棵二叉树根节点为起点,沿着二叉树外沿,逆时针走一圈回到根节点,路上遇到的元素顺序,就是先序遍历的结果
在这里插入图片描述

//二叉树前序遍历  根  左子树  右子树
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		printf("%c ", root->val);
		PreOrder(root->left);
		PreOrder(root->right);
	}
}

4.2.2二叉树的中序递归遍历

中序遍历也可以看成,二叉树每个节点,垂直方向投影下来(可以理解为每个节点从最左边开始垂直掉到地上),然后从左往右数,得出的结果便是中序遍历的结果。
在这里插入图片描述

//二叉树中序遍历  左子树  根  右子树
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		InOrder(root->left);
		printf("%c ", root->val);
		InOrder(root->right);
	}
}

4.2.3二叉树的后序递归遍历

后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

后序遍历就像是剪葡萄,我们要把一串葡萄剪成一颗一颗的。
就是围着树的外围绕一圈,如果发现一剪刀就能剪下的葡萄(必须是一颗葡萄)(也就是葡萄要一个一个掉下来,不能一口气掉超过1个这样),就把它剪下来,组成的就是后序遍历了。
在这里插入图片描述

//二叉树后序遍历   左子树 右子树  根
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		PostOrder(root->left);
		PostOrder(root->right);
		printf("%c ", root->val);
	}
}

口诀

前序遍历: 先根 再左 再右

中序遍历: 先左 再根 再右

后序遍历: 先左 再右 再根

4.2.4二叉树的层序遍历

层次遍历很好理解,就是从根节点开始,一层一层,从上到下,每层从左到右,依次写值就可以了。
在这里插入图片描述
在这里插入图片描述

//层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	Init_Queue(&q);
	//先入根节点
	Push_Queue(&q, root);
	while (!Empty(&q))
	{
		BTNode* front = Front_Queue(&q);
		//出当前节点
		Pop_Queue(&q);
		printf("%c ", front->val);

		if (front->left != NULL)
		{
			Push_Queue(&q,front->left);
		}
		if (front->right != NULL)
		{
			Push_Queue(&q, front->right);
		}
	}
	printf("\n");
}

4.3二叉树节点数

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

4.4二叉树叶子节点数

当一个节点的左孩子和右孩子都为空时,该节点就是叶子节点。

//二叉数叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
	//当左孩子和右孩子都是空的就是叶子结点
	if (root == NULL)
	{
		return 0;
	}
	if (root->left = NULL && root->right == NULL)
	{
		return 1;
	}
	else
	{
		return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
	}
}

4.5二叉树第K层节点数

求第K层节点数的问题可以转换成左子树第K-1层节点数+右子树第K-1层节点数,由此递归下去。

//二叉树第K层节点数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k==1)
	{
		return 1;
	}
	int LeftKSize = BinaryTreeLevelKSize(root->left, k - 1);
	int RightKSize = BinaryTreeLevelKSize(root->right, k - 1);
	return LeftKSize + RightKSize;
}

4.6二叉树查找值为X的节点

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

4.7二叉树的深度

//二叉树深度
int BinaryDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int LeftDepth = BinaryDepth(root->left);
	int RightDepth = BinaryDepth(root->right);

	return LeftDepth > RightDepth ? LeftDepth + 1 : RightDepth + 1;
}

4.8判断二叉树是否是完全二叉树

判断的思路:
在这里插入图片描述

//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	Init_Queue(&q);
	Push_Queue(&q, root);
	while (Empty(&q))
	{
		BTNode* front = Front_Queue(&q);
		Pop_Queue(&q);

		if (front)
		{
			Push_Queue(&q, front->left);
			Push_Queue(&q, front->right);
		}
		else
		{
			break;
		}
	}
	while (!Empty(&q))
	{
		BTNode* front = Front_Queue(&q);
		if (front != NULL)
		{
			Destory_Queue(&q);
			return false;
		}
		Pop_Queue(&q);
	}
	Destory_Queue(&q);
	return true;
}

4.9二叉树完整代码

.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"Queue.h"

typedef char BTDataType;
typedef struct BinaryTree
{
	struct BinaryTree* left;
	struct BinaryTree* right;
	BTDataType val;
}BTNode;

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

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

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

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

//二叉树结点个数
int BinaryTreeSize(BTNode* root);

//二叉数叶子结点个数
int BinaryTreeLeafSize(BTNode* root);

//二叉树第K层节点数
int BinaryTreeLevelKSize(BTNode* root, int k);

//二叉树查找值为X的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode * root);

//二叉树深度
int BinaryDepth(BTNode* root);

//二叉树销毁
void BinaryTreeDestory(BTNode *root);

.c文件

#include"BinaryTree.h"

//二叉树前序遍历  根  左子树  右子树
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		printf("%c ", root->val);
		PreOrder(root->left);
		PreOrder(root->right);
	}
}

//二叉树中序遍历  左子树  根  右子树
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		InOrder(root->left);
		printf("%c ", root->val);
		InOrder(root->right);
	}
}

//二叉树后序遍历   左子树 右子树  根
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		PostOrder(root->left);
		PostOrder(root->right);
		printf("%c ", root->val);
	}
}

//层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	Init_Queue(&q);
	Push_Queue(&q, root);
	while (!Empty(&q))
	{
		BTNode* front = Front_Queue(&q);
		Pop_Queue(&q);
		printf("%c ", front->val);

		if (front->left != NULL)
		{
			Push_Queue(&q,front->left);
		}
		if (front->right != NULL)
		{
			Push_Queue(&q, front->right);
		}
	}
	printf("\n");
}

//二叉树结点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	else
	{
		return 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;
	}
	else
	{
		return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
	}
}

//二叉树第K层节点数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k==1)
	{
		return 1;
	}
	int LeftKSize = BinaryTreeLevelKSize(root->left, k - 1);
	int RightKSize = BinaryTreeLevelKSize(root->right, k - 1);
	return LeftKSize + RightKSize;
}

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

//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	Init_Queue(&q);
	Push_Queue(&q, root);
	while (Empty(&q))
	{
		BTNode* front = Front_Queue(&q);
		Pop_Queue(&q);

		if (front)
		{
			Push_Queue(&q, front->left);
			Push_Queue(&q, front->right);
		}
		else
		{
			break;
		}
	}

	while (!Empty(&q))
	{
		BTNode* front = Front_Queue(&q);
		if (front != NULL)
		{
			Destory_Queue(&q);
			return false;
		}
		Pop_Queue(&q);
	}
	Destory_Queue(&q);
	return true;

}

//二叉树深度
int BinaryDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int LeftDepth = BinaryDepth(root->left);
	int RightDepth = BinaryDepth(root->right);

	return LeftDepth > RightDepth ? LeftDepth + 1 : RightDepth + 1;
}

//二叉树销毁  使用递归  先释放左子树在释放右子树最后释放根
void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);

	free(root);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯刷题冲刺 | 倒计时26天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.路径2.特别数的和3.MP3储存4.求和1.路径 题目 链接&#xff1a; 路径 - 蓝桥云课 (lanqiao.cn…

算法学习之二分查找

&#x1f383;个人主页&#x1f383;&#xff1a;勇敢的小牛儿 &#x1f9e8;推荐专栏&#x1f9e8;&#xff1a;C语言知识点 ✨座右铭✨&#xff1a;敢于尝试才有机会 ⚠️今日鸡汤⚠️&#xff1a;Is the true wisdom fortitude ambition. -- Napoleon 真正的才智是刚毅的志向…

【云原生·Docker】常用命令

目录 &#x1f341;1、管理命令 &#x1f341;2、帮助命令 &#x1f341;3、镜像命令 &#x1f341;4、容器命令 &#x1f342;4.1.查看容器 &#x1f342;4.2.创建容器 &#x1f342;4.3.删除容器 &#x1f342;4.4.拷贝文件 &#x1f342;4.5.查看容器IP &#x1f341;5、部署…

LSTM从入门到精通(形象的图解,详细的代码和注释,完美的数学推导过程)

先附上这篇文章的一个思维导图什么是RNN按照八股文来说&#xff1a;RNN实际上就是一个带有记忆的时间序列的预测模型RNN的细胞结构图如下&#xff1a;softmax激活函数只是我举的一个例子&#xff0c;实际上得到y<t>也可以通过其他的激活函数得到其中a<t-1>代表t-1时…

C语言/动态通讯录

本文使用了malloc、realloc、calloc等和内存开辟有关的函数。 文章目录 前言 二、头文件 三、主界面 四、通讯录功能函数 1.全代码 2.增加联系人 3.删除联系人 4.查找联系人 5.修改联系人 6.展示联系人 7.清空联系人 8.退出通讯录 总结 前言 为了使用通讯录时&#xff0c;可以…

Opencv项目实战:22 物体颜色识别并框选

目录 0、项目介绍 1、效果展示 2、项目搭建 3、项目代码展示与部分讲解 Color_trackbar.py bgr_detector.py test.py 4、项目资源 5、项目总结 0、项目介绍 本次项目要完成的是对物体颜色的识别并框选&#xff0c;有如下功能&#xff1a; &#xff08;1&#xff09;…

线程池的使用:如何写出高效的多线程程序?

目录1.线程池的使用2.编写高效的多线程程序Java提供了Executor框架来支持线程池的实现&#xff0c;通过Executor框架&#xff0c;可以快速地创建和管理线程池&#xff0c;从而更加方便地编写多线程程序。 1.线程池的使用 在使用线程池时&#xff0c;需要注意以下几点&#xff…

GDAL python教程基础篇(7)OGR空间计算

1.空间计算 地理数据处理&#xff08;geoprocessing&#xff09;计算函数&#xff1a; 多边形&#xff08;Polygon&#xff09;&#xff1a; 1、交&#xff1a;poly3.Intersection(poly2) 2、并&#xff1a;poly3.Union(poly2) 3、差&#xff1a;poly3.Difference(poly2) 4、补…

python打包成apk界面设计,python打包成安装文件

大家好&#xff0c;给大家分享一下如何将python程序打包成apk文件&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 1、如何用python制作十分秒加减的apk 如何用python制作十分秒加减的apk&#xff1f;用法:. apk包放入apk文件目录,然后输入…

Linux基础命令大全(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…

走进哈希心房

目录 哈希的概念 哈希函数 哈希冲突和解决方法 闭散列 插入 查找 删除 开散列 插入 查找 删除 哈希表&#xff08;开散列&#xff09;整体代码 位图 位图模拟实现思路分析&#xff1a; 位图应用 布隆过滤器 本文介绍unordered系列的关联式容器&#xff0c;unor…

安卓手机也可以使用新必应NewBing

没有魔法安卓手机也可以使用新必应NewBing 目前知道的是安卓手机 安卓手机先安装一个猴狐浏览器 打开手机自带浏览器&#xff0c;搜索关键词&#xff1a;猴狐浏览器&#xff0c;找到官网 也可以直接复制这个网址 狐猴浏览器 lemurbrowser CoolAPK 我的手机是荣耀安卓手机…

【正点原子FPGA连载】 第三十三章基于lwip的tftp server实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

第三十三章基于lwip的tftp server实验 文件传输是网络环境中的一项基本应用&#xff0c;其作用是将一台电子设备中的文件传输到另一台可能相距很远的电子设备中。TFTP作为TCP/IP协议族中的一个用来在客户机与服务器之间进行文件传输的协议&#xff0c;常用于无盘工作站、路由器…

「ML 实践篇」分类系统:图片数字识别

目的&#xff1a;使用 MNIST 数据集&#xff0c;建立数字图像识别模型&#xff0c;识别任意图像中的数字&#xff1b; 文章目录1. 数据准备&#xff08;MNIST&#xff09;2. 二元分类器&#xff08;SGD&#xff09;3. 性能测试1. 交叉验证2. 混淆矩阵3. 查准率与查全率4. P-R 曲…

2023年腾讯云服务器配置价格表(轻量服务器、CVM云服务器、GPU云服务器)

目前腾讯云服务器分为轻量应用服务器、云服务器云服务器云服务器CVM和GPU云服务器&#xff0c;首先介绍一下这三种服务。 1、腾讯云云服务器&#xff08;Cloud Virtual Machine&#xff0c;CVM&#xff09;提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源&#xff…

【经验总结】10年的嵌入式开发老手,到底是如何快速学习和使用RT-Thread的?(文末赠书5本)

【经验总结】一位近10年的嵌入式开发老手&#xff0c;到底是如何快速学习和使用RT-Thread的&#xff1f; RT-Thread绝对可以称得上国内优秀且排名靠前的操作系统&#xff0c;在嵌入式IoT领域一直享有盛名。近些年&#xff0c;物联网产业的大热&#xff0c;更是直接将RT-Thread这…

python绘制图像中心坐标二维分布曲线

数据和代码如下所示&#xff1a; import pandas as pd import numpy as np import matplotlib.pyplot as plt import xlrd from scipy.stats import multivariate_normal from mpl_toolkits.mplot3d import Axes3D np.set_printoptions(suppressTrue)# 根据均值、标准差,求指定…

SuperMap iMobile for Android 地图开发(一)

第一步&#xff1a;创建 Android Studio 项目 第一步&#xff1a;创建 Android Studio 项目 Android Studio 有两种创建项目的方法。 第一种是在 Android Studio起始页选择“Start a new Android Studio Project”。 第二种是在 Android Studio 主页选择“File”–>“New P…

数仓建模—主题域和主题

主题域和主题 前面在这个专题的第一篇,也就是数仓建模—数仓初识中我们就提到了一个概念—主题,这个概念其实在数仓的定义中也有提到 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。 今天我们主要来探究一下,数仓的主题到底是…

Multi-Camera Color Correction via Hybrid Histogram Matching直方图映射

文章目录Multi-Camera Color Correction via Hybrid Histogram Matching1. 计算直方图&#xff0c; 累计直方图&#xff0c; 直方图均衡化2. 直方图规定化&#xff0c;直方图映射。3. 实验环节3.1 输入图像3.2 均衡化效果3.3 映射效果4. 针对3实验环节的伪影 做处理和优化&…