【数据结构】二叉树链式结构补充和二叉树的顺序结构及实现

news2024/11/23 0:54:23

在这里插入图片描述

🐇

🔥博客主页: 云曦
📋系列专栏:数据结构

💨吾生也有涯,而知也无涯
💛 感谢大家👍点赞 😋关注📝评论

文章目录

  • 前言
  • 📚一、二叉树链式结构的接口补充
    • 📔1.1 二叉树第k层节点的个数
    • 📔1.2 二叉树查找值为x的节点
    • 📔1.3 判断一颗二叉树是否是完全二叉树
  • 📚二、二叉树的顺序结构
    • 📔2.1 二叉树顺序结构的概念
    • 📔2.2 堆实现
      • 📕2.2.1 堆的初始化
      • 📕2.2.2 堆的销毁
      • 📕2.2.3 堆的插入
        • 📃2.2.3.1 向上调整算法
      • 📕2.2.4 堆的删除
        • 📃2.2.4.1 向下调整算法
      • 📕2.2.5 获取堆顶元素
      • 📕2.2.6 检测堆是否为空
    • 📔2.3 堆排序
    • 📔2.4 TOPK问题
    • 📔2.5本篇章的代码

前言

上一期讲到了二叉树的链式结构,但上一期的链式结构还差着几个接口没写,所以在这一期补上,然后就是二叉树的顺序结构讲解了,二叉树的顺序结构将会实现堆和堆排序,最后会用堆实现TOPK问题。

📚一、二叉树链式结构的接口补充

📔1.1 二叉树第k层节点的个数

  • 思路:递归左右子树并且相加,层层进入且每次进入k都减1,当k等于1时就是第k层,然后返回1给上一层。
  • 需要注意的是,传入的k有可能小于0,所以要检查一下k
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	assert(k > 0);//检测k是否小于0
	
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}
	
	return BinaryTreeLevelKSize(root->left, k - 1) 
		+ BinaryTreeLevelKSize(root->right, k - 1);
}

此接口的递归展开图
在这里插入图片描述

📔1.2 二叉树查找值为x的节点

  • 思路:遍历找到k节点,但找到了返回也只是返回到上一层的函数栈帧的执行位置,所以解决方法就是,定义一个节点接收回归的值,然后判断这个节点是否等于或不等于NULL,需要注意的是左右子树都要判断一下,因为有可能要找的节点不在左子树,在右子树里。
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	
	if (root->val == x)
	{
		return root;
	}

	BTNode* ret = NULL;
	ret = BinaryTreeFind(root->left, x);
	if (ret)
	{
		return ret;
	}

	ret = BinaryTreeFind(root->right, x);
	if (ret)
	{
		return ret;
	}

	return NULL;
}

📔1.3 判断一颗二叉树是否是完全二叉树

  • 思路:跟层序遍历的思路差不多,只是这里要把NULL也入队列,然后出队列时,等于NULL就跳出循环,然后再循环出队列的数据。
  1. 如果有不等于NULL的节点,那么这颗树就不是完全二叉树。
  2. 遍历一遍后,没有返回,那么这棵树就是完全二叉树。
bool BinaryTreeComplete(BTNode* root)
{
	//创建及初始化队列
	Que q;
	QueueInit(&q);
	//把根不等于空(NULL)时入队列
	if (root)
	{
		QueuePush(&q, root);
	}
	
	//思路:上一层出带下一层进
	while (!QueueEmpty(&q))
	{
		BTNode* Front = QueueFront(&q);
		//当节点等于空时,break跳出循环
		if (Front == NULL)
		{
			break;
		}
		//NULL也入队列
		QueuePush(&q, Front->left);
		QueuePush(&q, Front->right);
		QueuePop(&q);
	}

	//继续出队列,此时如果遇到不等于空(NULL)的节点
	//那么这颗树就不是完全二叉树
	while (!QueueEmpty(&q))
	{

		BTNode* Front = QueueFront(&q);
		QueuePop(&q);
		if (Front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}

	}

	QueueDestroy(&q);
	//到这里时,已经遍历完整棵树了,此时这棵树就是完全二叉树
	return true;
}

📚二、二叉树的顺序结构

📔2.1 二叉树顺序结构的概念

  • 概念:普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统
    虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段
  • 堆的逻辑结构是一颗完全二叉树,但物理结构是一个数组
    在这里插入图片描述
  • 堆又分为小根堆(小堆)或大根堆(大堆)
  • 小堆:这颗完全二叉树的所有父亲节点的数据都小于孩子节点
  • 大堆:这颗完全二叉树的所有父亲节点的数据都大于孩子节点
    在这里插入图片描述
  • 查找一颗完全二叉树的父亲或左右孩子的方法:
  • leftchild = parent * 2 + 1(左孩子 = 父亲乘2加1)
  • right = parent * 2 + 2 (右孩子 = 父亲乘2加2)
  • parent = (child - 1) / 2 (父亲 = (孩子-1) / 2)
  • 堆的性质
  1. 堆中某个节点的值总是不大于或不小于其父节点的值
  2. 堆总是一棵完全二叉树。
  • 堆的结构
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arr;
	int size;
	int capacity;
}HP;

📔2.2 堆实现

  • 堆其实是用顺序表实现的,只是逻辑结构与顺序表有些差异

📕2.2.1 堆的初始化

  • 堆的初始化有两种结构
  1. 第一种结构:
void HeapInit(HP* php)
{
	assert(php);
	php->arr = NULL;
	php->capacity = 0;
	php->size = 0;
}
  • 第二种结构:

这种结构其实就是,给一个n个元素的数组,让我们把数组的数据拷贝到堆里然后建堆

void HeapInitArray(HP* php, HPDataType* arr, int n)
{
	assert(php);
	assert(arr);

	//开辟n个空间
	php->arr = (HPDataType*)malloc(sizeof(HPDataType)*n);
	if (php->arr == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	php->capacity = n;
	php->size = n;

	//把原数组的数据拷贝到在堆上开辟的数组里
	memcpy(php->arr, arr, sizeof(HPDataType) * n);

	//向上调整建堆
	int i = 0;
	for (i = 1; i < n; i++)
	{
		AdjustUp(php->arr, i);
	}

}

📕2.2.2 堆的销毁

堆的销毁跟顺序表一样的,释放开辟的空间,然后把容量和有效数据的个数置为0

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

📕2.2.3 堆的插入

在这里插入图片描述

  1. 把扩容的功能实现出来
//容量满了,扩容
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 
					INIT_SIZE : php->capacity * TIMES;
		HPDataType* tmp=(HPDataType*)realloc(php->arr, 
					sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->arr = tmp;
		php->capacity = newCapacity;
	}
  1. 然后把插入数据
	php->arr[php->size] = x;
	php->size++;
  1. 插入数据后,把数据向上调整,让这个数组变成堆
	AdjustUp(php->arr, php->size-1);
📃2.2.3.1 向上调整算法
  • 注意:向上调整算法的前提是:前面的数是堆
  1. 接收数组和插入数据的位置(n-1的位置)
  2. 计算父亲的位置,公式为:parent = (child - 1) / 2
  3. 让孩子和父亲比较,小于父亲就交换孩子和父亲的位置
  4. 然后把父亲的下标赋值给孩子,再计算父亲的位置
  5. 如果孩子大于父亲,那么就break跳出循环
  • 时间复杂度:O(logN)
    在这里插入图片描述
void AdjustUp(int* arr, int child)
{
	int parent = (child - 1) / 2;//计算父亲的位置
	//child等于0时,为循环结束的条件
	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);//交换函数
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			//孩子大于父亲时跳出循环
			break;
		}

	}

}
  • 测试:
int main()
{
	int arr[] = { 65,100,70,32,50,60 };
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		HeapPush(&hp, arr[i]);
	}
	HeapPrint(&hp);
	HeapDestroy(&hp);

	return 0;
}

在这里插入图片描述

📕2.2.4 堆的删除

堆的删除,删尾没有任何意义,但把首尾元素交换一下,那么每次删除的都是最小/最大的元素,配合获取堆顶元素的接口,可以实现排序了
思路:

  1. 先将首尾元素交换
  2. size减1
  3. 最后向下调整建堆,向下调整只影响尾元素的祖先,不会影响其他的元素
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	//交换首尾的数据
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	php->size--;
	//然后向下调整
	AdjustDown(php->arr, php->size, 0);
}
📃2.2.4.1 向下调整算法
  • 向下调整的前提是:左右孩子都是小堆 / 大堆
  1. 先找出左右孩子最小的哪一个,那么就要计算孩子的位置,但这里有个小技巧,先默认左孩子是最小的,然后再判断,如果右孩子小于左孩子child就加1变成右孩子
  2. 此时,左右孩子谁小我们不关心,判断孩子是否小于父亲,孩子小于父亲,那么就交换孩子和父亲的位置,把孩子的下标赋值给父亲,再计算孩子的下标
  3. 孩子大于父亲,就证明堆建好了,break跳出循环
  • 时间复杂度:O(logN)
    在这里插入图片描述
void AdjustDown(int* arr, int n, int parent)
{
	//默认选择左孩子
	int child = parent * 2 + 1;
	while (child < n)
	{
		//左孩子表示最小的
		//那么改为右孩子
		if (child+1 < n && arr[child + 1] < arr[child])
		{
			++child;
		}

		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			//孩子大于父亲,就跳出循环
			break;
		}

	}

}

📕2.2.5 获取堆顶元素

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->arr[0];
}

📕2.2.6 检测堆是否为空

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

📔2.3 堆排序

  • 堆排序要注意两个点:
  • 排升序建大堆
  • 排降序建小堆
  • 向上调整实现堆排序,时间复杂度:O(N*logN)
  1. 循环从数组的第二个元素开始向上调整
  2. 循环从最后一个元素开始往前和堆顶元素交换位置,再向下调整
    在这里插入图片描述
void HeapSort(int* arr, int n)
{
	//向上调整建堆O(N*logN)
	int i = 0;
	for (i = 1; i < n; i++)
	{
		AdjustUp(arr, i);
	}
	
	int end = n-1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}

}
  • 向下调整实现堆排序,时间复杂度:O(N)
  1. 从倒数第一个非叶子节点开始调(也就是最后一个节点的父亲)
  • 找到最后一个节点的父亲的方法:
  • n-1找到最后一个元素,再按公式parent = (child-1)/2,就可以找到最后一个节点的父亲了,也就是:(n-1-1) / 2
  1. 向下调整后减1就可以找到下一个非叶子节点的位置,因为物理结构是一个数组,数组存储的元素是连续的
  2. 循环从最后一个元素开始往前和堆顶元素交换位置,再向下调整
    在这里插入图片描述
void HeapSort(int* arr, int n)
{
	//向下调整建堆O(N)
	int i = 0;
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}

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

}

📔2.4 TOPK问题

在这里插入图片描述

  • 时间复杂度:O(N*logK)
  • 空间复杂度:O(K)
  1. 首先要制造一些数据到文件里
void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand((unsigned int)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);
}
int main()
{
	//CreateNDate();
	//传入文件名和要k的数值
	PrintTopK("data.txt", 10);

	return 0;
}
  1. 打开文件,把前k个数据输入到堆里,然后向下调整建堆
void PrintTopK(const char* filename, int k)
{
	FILE* pf = fopen(filename, "r");
	if (pf == NULL)
	{
		perror("fopen fail");
		exit(-1);
	}

	int* heap = (int*)malloc(sizeof(int) * k);
	if (heap == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	
	// 1、取出前k个数据建堆
	int i = 0;
	for (i = 0; i < k; i++)
	{
		fscanf(pf, "%d", &heap[i]);
	}

	//2.、前k个数向下调整,建堆
	//k-1找到最后一个元素的下标
	//(k-1-1)/2找到最后一个节点的父亲节点
	for (i=(k-1-1)/2; i>=0; i--)
	{
		AdjustDown(heap, k, i);
	}

	fclose(pf);
	free(heap);
	pf = NULL;
	heap = NULL;
}
  1. 读取剩下的数据,与堆顶比较,大于堆顶就替换进堆,然后再向下调整,建堆
void PrintTopK(const char* filename, int k)
{
	FILE* pf = fopen(filename, "r");
	if (pf == NULL)
	{
		perror("fopen fail");
		exit(-1);
	}

	int* heap = (int*)malloc(sizeof(int) * k);
	if (heap == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	
	// 1、取出前k个数据建堆
	int i = 0;
	for (i = 0; i < k; i++)
	{
		fscanf(pf, "%d", &heap[i]);
	}

	//2.、前k个数向下调整,建堆
	for (i=(k-1-1)/2; i>=0; i--)
	{
		AdjustDown(heap, k, i);
	}

	
	// 读取剩下的数据依次跟堆顶数据比较,
	//大于堆顶就替换进堆,然后再向下调整
	int x = 0;
	while (fscanf(pf, "%d", &x) != EOF)
	{
		//大于堆顶就替换它进堆
		if (x > heap[0])
		{
			heap[0] = x;
			//替换后,再向下调整
			AdjustDown(heap, k, 0);
		}
		
	}

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

	fclose(pf);
	free(heap);
	pf = NULL;
	heap = NULL;
}
  • 测试
  • 这里有一个调试小技巧,我们写入文件的是随机数,不知道是不是最大的前k个,那么我们就自己在随机位置加入k个大的数值,如果输出出来的是我们自己改的数值,那么程序就是正确的
  • 从99991 - 999910都是我自己更改的测试数值
    在这里插入图片描述
  • 测试结果:
    在这里插入图片描述

📔2.5本篇章的代码

堆的实现代码

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

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

相关文章

机器人过程自动化(RPA)入门 8. 异常处理、调试和日志记录

有时,自动化程序可能无法执行。为了处理此类情况,我们使用异常处理活动。在本章中,我们将从UiPath中可用的各种类型的异常处理方法、您可能遇到的异常以及如何处理它们开始。我们还将学习日志记录。本章涉及的一个重要主题是调试,以检查工作流是否正常工作,并更正任何错误…

Neural Networks for Fingerprint Recognition

Neural Computation ( IF 3.278 ) 摘要&#xff1a; 在采集指纹图像数据库后&#xff0c;设计了一种用于指纹识别的神经网络算法。当给出一对指纹图像时&#xff0c;算法输出两个图像来自同一手指的概率估计值。在一个实验中&#xff0c;神经网络使用几百对图像进行训练&…

Python学习之索引与切片

Python学习之索引与切片 s “0abcdefghijklmnopqrstuvwxyz”&#xff0c;第一个元素‘0’&#xff0c;索引号为0&#xff0c;最后一个元素‘z’&#xff0c;索引号为26 1. s[0]获取索引号为0的元素 2. s[1:3]获取索引号为1的元素&#xff0c;直到但不包括索引号为3的元素。即…

大数据-玩转数据-Flink Sql 窗口

一、说明 时间语义&#xff0c;要配合窗口操作才能发挥作用。最主要的用途&#xff0c;当然就是开窗口然后根据时间段做计算了。Table API和SQL中&#xff0c;主要有两种窗口&#xff1a;分组窗口&#xff08;Group Windows&#xff09;和 含Over字句窗口&#xff08;Over Win…

S32K144 GPIO编程

前面的文章介绍了如何在MDK-Keil下面进行S32K144的开发&#xff0c;下面就使用该工程模板进行GPIO LED的编程试验。 1. 开发环境 S32K144EVB-Q100开发板MDK-Keil Jlink 2. 硬件连接 S32K144EVB-Q100开发板关于LED的原理图如下&#xff1a; 也就是具体连接关系如下&#xf…

【知识点随笔分析 | 第五篇】简单介绍什么是QUIC

前言&#xff1a; 随着互联网的快速发展&#xff0c;传统的基于TCP的协议开始显现出一些局限性。TCP在连接建立和拥塞控制方面存在一定的延迟&#xff0c;这可能导致用户在访问网页、观看视频或玩网络游戏时感受到不必要的等待时间。而QUIC作为一种新兴的传输协议&#xff0c;试…

Java编程技巧:swagger2、knif4j

目录 1、springbootswagger2knif4j2、springbootswagger3knif4j3、springcloudswagger2knif4j 1、springbootswagger2knif4j 2、springbootswagger3knif4j 3、springcloudswagger2knif4j 注意点&#xff1a; Api注解&#xff1a;Controller类上的Api注解需要添加tags属性&a…

P1311 [NOIP2011 提高组] 选择客栈(小小的也很可爱【指小动规】)

[NOIP2011 提高组] 选择客栈 题目描述 丽江河边有 n n n 家很有特色的客栈&#xff0c;客栈按照其位置顺序从 1 1 1 到 n n n 编号。每家客栈都按照某一种色调进行装饰&#xff08;总共 k k k 种&#xff0c;用整数 0 ∼ k − 1 0 \sim k-1 0∼k−1 表示&#xff09;&am…

stm32 - 中断

stm32 - 中断 中断向量表NVIC 嵌套中断向量控制器优先级 中断向量表 自定义的中断服务函数&#xff0c;由编译器随机指定函数地址 stm32的中断&#xff0c;由于硬件的限制&#xff0c;只能跳到固定的地址执行程序 为了能让硬件跳转到一个不固定的中断函数中&#xff0c; 需要在…

基于Qt Creator开发的坦克大战小游戏

目录 介绍开发环境技术介绍安装说明项目目录设计思想项目介绍运行演示知识点记录Gitee源码链接 介绍 &#xff01;&#xff01;&#xff01;资源图片是从网上免费下载&#xff0c;源码都是原创&#xff0c;供个人学习使用&#xff0c;非盈利&#xff01;&#xff01;&#xff…

Acwing 836. 合并集合

Acwing 836. 合并集合 题目描述思路讲解代码展示 题目描述 思路讲解 并查集&#xff1a;代码短&#xff0c;思路精巧&#xff0c;面试常见。 近乎O&#xff08;1&#xff09;的时间复杂度。 代码展示 #include<iostream>using namespace std;const int N 100010; in…

Python海洋专题四之水深地图图像修饰

Python海洋专题四之水深地图图像修饰 海洋与大气科学 上期会修改画布大小、坐标轴字体 没有对数据本身进行修改 本期内容 1&#xff1a;修改colormap&#xff01; 2&#xff1a;倒置colormap 3&#xff1a;加上colorbar、调整其显示位置和字体大小 1&#xff1a;修改colo…

OpenCV实现视频的追踪(meanshift、Camshift)

目录 1&#xff0c;meanshift 1.1 算法流程 1.2 算法实现 1.3 代码实现 1.4 结果展示 1&#xff0c;meanshift 1.1 算法流程 1.2 算法实现 1.3 代码实现 import numpy as np import cv2 as cv# 读取视频 cap cv.VideoCapture(video.mp4)# 检查视频是否成功打开 if n…

意大利储能公司【Energy Dome】完成1500万欧元融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于意大利米兰的储能公司Energy Dome今日宣布已完成1500万欧元B轮融资。 本轮融资完成后&#xff0c;Energy Dome的融资总额已经达到了5500万欧元&#xff0c;本轮融资的参与者包括阿曼创新发…

【Java 进阶篇】JDBC插入数据详解

在Java应用程序中&#xff0c;与数据库交互是一项常见的任务。其中&#xff0c;插入数据操作是一种基本的数据库操作之一。本文将详细介绍如何使用Java JDBC&#xff08;Java Database Connectivity&#xff09;来执行插入数据操作。无论您是初学者还是有一定经验的开发人员&am…

好题分享

1.Problem - G - Codeforces &#xff08;1&#xff09;题意 &#xff08;2&#xff09;思路 因为最多13次&#xff0c;那么不如我们就问13次&#xff0c;然后考虑把每一个位置重新按二进制拆分成一个下标&#xff0c;因为C(13,6) > 1000,因此在数量上是满足得&#xff0c;我…

【centos7】centos7卸载gitlab

一、GitLab安装 1. 安装依赖包 yum install -y curl policycoreutils-python openssh-server 2. 安装lrzsz&#xff08;如已经安装可忽略&#xff09; yum -y install lrzsz 3. 下载rpm包 cd /usr/local wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gi…

spring security(二)--授权

零.前情提要 这篇文章主要借鉴B站三更大大关于spring security的教程&#xff0c;这篇文章的大部分内容也来自于那个教程&#xff0c;写这个的主要目的是记录加强印象&#xff0c;总结&#xff0c;并且在文章中我也有穿插自己的想法。 前面的文章【spring security教程&#…

java Spring Boot 将日志写入文件中记录

我们之前的一套操作来讲 日志都是在控制台上的 但 如果你的项目在正式环境上跑 运维人员突然告诉你说日志报错了&#xff0c;但你日志只在控制台上&#xff0c;那公司项目如果访问量很大 那你是很难在控制台上找到某一条日志的 这时 我们就可以用文件把它记下来 我们打开项目 …

python使用mitmproxy和mitmdump抓包在电脑上抓包(二)

在我的上篇文章中&#xff0c;主要记录如何安装mitmproxy和抓取https流量。参考链接&#xff1a; python使用mitmproxy和mitmdump抓包在电脑上抓包-CSDN博客 本篇主要使用python配合mitmdump来抓包和处理返回包&#xff0c;更加灵活&#xff0c;这也是mitmproxy(mitmdump)的最…