【数据结构】二叉树(二)——顺序结构

在这里插入图片描述

前言
本篇博客讲解数组实现二叉树的顺序结构


文章目录

  • 一、二叉树的顺序结构及实现
    • 1.1 二叉树的顺序结构
    • 1.2 堆的概念
    • 1.3 堆的实现
      • 1.3.1 初始化堆
      • 1.3.2 向堆中插入元素
      • 1.3.3 从堆顶删除
      • 1.3.4 其他操作
      • 1.3.5 完整代码
        • Heap.h
        • Heap.c
    • 1.4 堆的应用
      • 1.4.1 堆排序
      • 1.4.2 TOP-K问题

一、二叉树的顺序结构及实现

1.1 二叉树的顺序结构

一般来说,顺序结构(数组)通常会用来实现完全二叉树,顺序结构用来实现不完全二叉树不是好的想法,因为会浪费许多空间。
在这里插入图片描述

1.2 堆的概念

堆(Heap)是一种特殊的树形数据结构,堆常常被用于优先队列的实现,因为它支持快速查找和删除具有最高或最低优先级的元素。堆分为最大堆和最小堆,这两者都是二叉堆的一种形式。同时堆是完全二叉树。

最大堆和最小堆:

  1. 最大堆(Max Heap): 在最大堆中,父节点的值大于或等于其每个子节点的值。这意味着树的根节点包含最大的值。

  2. 最小堆(Min Heap): 在最小堆中,父节点的值小于或等于其每个子节点的值。这样,树的根节点包含最小的值。

堆一般是一个完全二叉树,但并不要求是满二叉树。
在这里插入图片描述

1.3 堆的实现

// 堆的数据类型
typedef int HPDataType;

// 堆的结构体
typedef struct Heap {
    HPDataType* a;      // 数组,用于存储堆的元素
    int size;           // 当前堆中的元素个数
    int capacity;       // 堆的容量,即数组的大小
} HP;

1.3.1 初始化堆

void HeapInit(HP* php) {
	assert(php);

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

1.3.2 向堆中插入元素

这里以最小堆为例
在这里插入图片描述
代码实现:

//元素交换位置
void Swap(HPDataType* p1 , HPDataType* p2) {
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

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

		//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序
		if (a[child] < a[parent]) {
			Swap(&a[child],&a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else {
			break;
		}
	}
}

// 入堆操作
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);
}

1.3.3 从堆顶删除

删除堆顶元素的思路通常涉及将堆顶元素与堆中最后一个元素交换,然后删除最后一个元素,最后执行一次向下调整(AdjustDown)操作,以维护堆的性质。
在这里插入图片描述
代码实现:

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

		//a[child + 1] < a[parent]小堆排序;a[child + 1] > a[parent]大堆排序
		if (child + 1 < size && a[child + 1] < a[child]) {
			++child;
		}

		//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序
		if (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);
}

1.3.4 其他操作

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

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

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

	return php->a[0];
}

//元素个数
size_t HeapSize(HP* php) {
	assert(php);

	return php->size;
}

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

	return php->size == 0;
}

1.3.5 完整代码

Heap.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.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);
Heap.c
#include "Heap.h"

void HeapInit(HP* php) {
	assert(php);

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

void HeapDestroy(HP* php) {
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 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 (child > 0) {

		//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序
		if (a[child] < a[parent]) {
			Swap(&a[child],&a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else {
			break;
		}
	}
}

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

void AdjustDown(int* a, int size, int parent) {
	int child = parent * 2 + 1;
	
	while (child < size) {

		//a[child + 1] < a[parent]小堆排序;a[child + 1] > a[parent]大堆排序
		if (child + 1 < size && a[child + 1] < a[child]) {
			++child;
		}

		//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序
		if (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->a);

	return php->a[0];
}

size_t HeapSize(HP* php) {
	assert(php);

	return php->size;
}

bool HeapEmpty(HP* php) {
	assert(php);

	return php->size == 0;
}

1.4 堆的应用

1.4.1 堆排序

根据堆的特性,可以用堆来进行排序,过程如下:

  1. 建堆: 将待排序的数组构建成一个最大堆或最小堆。

  2. 排序: 利用删除堆顶元素的思想,交换堆顶元素(根节点)与数组末尾元素,然后重新调整堆,使其保持最大堆或最小堆的性质。这个步骤只需要执行 n - 1 次,其中 n 是数组的长度。每次执行后,最大(或最小)元素会被移到数组的末尾。重复步骤 2 直到整个数组有序。

在这里插入图片描述

代码实现: 基于堆的实现

#include "Heap.h"

//降序
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;
	}
}

int main() {
	int a[] = { 1,5,4,3,6,9,10 };
	int n = sizeof(a)/sizeof(a[0]);
	HeapSort(a, n);
	int i = 0;
	while (i < n) {
		printf("%d ",a[i]);
		i++;
	}
	return 0;
}

输出结果:
在这里插入图片描述


在建立堆时,有向上调整和向下调整两种方式:

	//向上调整,时间复杂度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);
	}

在这里插入图片描述
向上调整的时间复杂度为 T ( h ) = 1 × 2 1 + 2 × 2 2 + 3 × 2 3 + . . . . . . + ( h − 1 ) × 2 ( h − 1 ) T(h)=1×2^1+2×2^2+3×2^3+......+(h-1)×2^{(h-1)} T(h)=1×21+2×22+3×23+......+(h1)×2(h1),通过错位相减后得到 T ( h ) = − ( 2 h − 1 ) + 2 h ( h − 1 ) + 1 T(h)=-(2^h-1)+2^{h(h-1)}+1 T(h)=(2h1)+2h(h1)+1
因为树的高度h与节点数N之间的关系是 2 h − 1 = N 2^h-1=N 2h1=N
h = l o g 2 ( N + 1 ) h=log_2(N+1) h=log2(N+1)。用N代替h得到,时间复杂度 T ( N ) = − N + ( N + 1 ) ( l o g 2 ( N + 1 ) − 1 ) + 1 , 近似于 O ( N ) = N ∗ l o g 2 N T(N)=-N+(N+1)(log_2(N+1)-1)+1,近似于O(N)=N*log_2N T(N)=N+(N+1)(log2(N+1)1)+1,近似于O(N)=Nlog2N
在这里插入图片描述
向下调整的时间复杂度为 T ( h ) = ( h − 1 ) × 2 1 + ( h − 2 ) × 2 2 + ( h − 3 ) × 2 3 + . . . . . . + 1 × 2 ( h − 1 ) T(h)=(h-1)×2^1+(h-2)×2^2+(h-3)×2^3+......+1×2^{(h-1)} T(h)=(h1)×21+(h2)×22+(h3)×23+......+1×2(h1),通过错位相减后得到 T ( h ) = 2 h − 1 − h T(h)=2^h-1-h T(h)=2h1h
同样,用N代替h得到,时间复杂度 T ( N ) = N − l o g 2 ( N + 1 ) , 近似于 O ( N ) = N T(N)=N-log_2(N+1),近似于O(N)=N T(N)=Nlog2(N+1),近似于O(N)=N

因此,得出结论,在使用堆进行排序时,利用向下调整法来构建堆更加高效。

1.4.2 TOP-K问题

Top-k 问题是在一组数据中找出前 k 个最大或最小的元素的问题。这个问题在实际应用中经常出现,例如在搜索引擎中找出前 k 个相关度最高的页面,或者在数据分析中找出前 k 个热门商品。

最直观的方法是对整个数据集进行排序,然后取前 k 个或后 k 个元素。这种方法的时间复杂度通常为 O(n log n),其中 n 是数据集的大小。这在 k 远小于 n 的情况下是不划算的。

我们可以通过维护一个大小为 k 的最小堆,可以在 O(n log k) 的时间内找到前 k 个最大元素。

方法如下:

  1. 创建一个大小为 k 的堆: 初始化一个大小为 k 的堆,如果是求前 k 个最小元素,则使用最大堆(大顶堆),如果是求前 k 个最大元素,则使用最小堆(小顶堆)。

  2. 将前 k 个元素插入堆中: 遍历数组的前 k 个元素,依次插入堆中。

  3. 遍历数组的剩余元素: 从第 k+1 个元素开始遍历数组(对于大量数据显然是不合适的,所以可以从文件中挨个读取),对于每个元素,如果它比堆顶元素大(或小,取决于是求最大还是最小元素),则替换堆顶元素,并重新调整堆,以保持堆的性质。

  4. 输出结果: 遍历堆中的元素,即为前 k 个最大或最小元素。

要在大量数据中找出前k个最大的数字,如果全部放到数组中,所需要的空间很大,因此我们可以生成一个包含大量随机数据的文件。
代码如下:

//头文件中需要包含的头文件
//#include <time.h>

// 随机生成大量数据
void CreateDate() {
    int n = 100000;
    srand(time(0));
    const char* file = "date.txt";
    FILE* fin = fopen(file, "w");
    if (fin == NULL) {
        perror("fopen error");
        return;
    }

    for (int i = 0; i < n; i++) {
    
        //保证生成的随机数小于100000
        int x = (rand() + i) % 100000;
        fprintf(fin, "%d\n", x);
    }
    fclose(fin);
}

// 从文件中读取数据并输出前k个最大值
void PrintfTopK(const char* file, int k) {
    FILE* fout = fopen(file, "r");
    if (fout == NULL) {
        perror("fopen error");
        return;
    }

    int* heap = (int*)malloc(sizeof(int) * k);
    if (heap == NULL) {
        perror("malloc error");
        return;
    }

    // 初始化堆
    for (int i = 0; i < k; i++) {
        fscanf(fout, "%d", &heap[i]);
        AdjustUp(heap, i);
    }

    int x = 0;
    // 逐个读取文件中的数据
    while (fscanf(fout, "%d", &x) != EOF) {
        // 如果读取的数比堆顶元素大,则更新堆
        if (x > heap[0]) {
            heap[0] = x;
            AdjustDown(heap, k, 0);
        }
    }

    // 输出前k个最大值
    for (int i = 0; i < k; i++) {
        printf("%d ", heap[i]);
    }

    free(heap);
    fclose(fout);
}

// 主函数
int main() {
    // 生成随机数据文件
    CreateDate();

    // 输出文件中的前5个最大值
    PrintfTopK("date.txt", 5);

    return 0;
}

我们要查看所有的数据,依此判断是否成功找出前k个最大数是不容易的,所以我们在生成的文件中,将5个随机生成的数改成比100000大的数,当我们输出时,只需要检查最大的5个数是不是这几个,即可知道排序是否正确。
在这里插入图片描述
输出结果:
在这里插入图片描述


在这里插入图片描述
如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。

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

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

相关文章

Ubuntu18.04 升级Ubuntu20.04

文章目录 背景升级方法遇到的问题 背景 因项目环境需要&#xff0c;欲将Ubuntu18.04升级至Ubuntu20.04&#xff0c;参考网上其他小伙伴的方法&#xff0c;也遇到了一个问题&#xff0c;特此记录一下&#xff0c;希望能帮助其他有同样问题的小伙伴。 升级方法 参考&#xff1a…

【React系列】Hook(一)基本使用

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 认识hook 1.1. 为什么需要hook Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下…

计算机研究生论文检索方法汇总

计算机研究生论文检索方法汇总 作为一名优质(冤种)计算机在读研究生&#xff0c;检索论文是一项不可或缺的技能之一。 一、paperwithcode paperswithcode是一个免费开放的资源平台&#xff0c;提供了机器学习领域的论文、代码、数据集、方法和评估表。在这里我们可以检索不同…

题目:大石头的搬运工(蓝桥OJ 3829)

问题描述&#xff1a; 解题思路&#xff1a; 官方&#xff1a; 注意点&#xff1a; 1.直观方法无法使用&#xff0c;因为其时间复杂度为O(n2)。带入题目数据n最大为1e5则时间复杂度为1e10&#xff0c;超过了运行限制&#xff08;默认1e8&#xff09;。 2.pair不会自动排序&…

vscode安装Prettier插件,对vue3项目进行格式化

之前vscode因为安装了Vue Language Features (Volar)插件&#xff0c;导致Prettier格式化失效&#xff0c;今天有空&#xff0c;又重新设置了一下 1. 插件要先安装上 2. 打开settings.json {"editor.defaultFormatter": "esbenp.prettier-vscode","…

Hex2Bin转换工具文档、Bootloader 、OTA 、STM32等MCU适用

说明&#xff1a;这个工具可以将 Hex 文件 转换为 Bin 格式文件&#xff0c;软件是按自己开发 STM32 OAT 功能需求开发的一款辅助 上位机软件。 有兴趣的朋友可留言探讨。 附加功能&#xff1a; 1.另外可以生成指定大小的bin 格式文件&#xff0c;文件多余的空余位置填充随机…

GZ075 云计算应用赛题第6套

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷6 某企业根据自身业务需求&#xff0c;实施数字化转型&#xff0c;规划和建设数字化平台&#xff0c;平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”&#xff0c;拟采用开源OpenSt…

可狱可囚的爬虫系列课程 10:在网站中寻找 API 接口

上一篇文章我们讲述了爬虫中一个比较重要的知识点&#xff0c;如何从 API 接口中获取数据&#xff0c;本篇文章我们继续讲述&#xff0c;如何在网站中寻找 API 接口&#xff0c;我们以“今日头条”网站 https://www.toutiao.com/ 为例。 如上图所示&#xff0c;如果要获取页面…

JumpServer3.0版本-账号管理

账号列表 我这里已经创建好了所以有很多,可以点击资产树列表分类查看 点击创建按钮,添加账号 资产:如果多个设备的账号密码一致可以在资产同事选中 名称:方便辨识即可 用户名:登录设备的账户名 密码:按你登录需求自行选择 添加按钮旁边还有个“模版添加” 此功能便…

Linux第3步_安装Ubuntu操作系统

创建好虚拟机后&#xff0c;就可以安装Ubuntu操作系统了。 1、双击“VMware Workstation Pro”&#xff0c;得到下面的界面。 2、点击“编辑虚拟机设置”&#xff0c;见下图&#xff1a; 3、等几秒钟&#xff0c;得到下面的界面&#xff1a; 4、点击“CD/DVD”&#xff0c;得到…

【SpringBoot】Java MVC 集成 Swagger 生成 API 文档

使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。官网: https://swagger.io/ Knife4j 是为Java MVC框架集成Swagger生成Api文档的增强解决方案。 <dependency><groupId>com.github.xiaoymin</groupI…

七款人体感应报警器电路图

人体感应报警器电路图&#xff08;一&#xff09; 人体发出的红外线波长在9&#xff5e;10um之间&#xff0c;属远红外线区。我们利用热释电红外传感器及信号处理集成电路&#xff0c;组装成一个人体红外线感应开关电路报警器&#xff0c;它能依靠人体发出的微量红外线进行开关…

how2heap-2.23-07-unsafe_unlink

unlink的作用 在glibc-2.23的malloc.c中搜索unlink&#xff0c;找到unlink的使用场景 _int_malloc 从恰好大小合适的largebin中获取chunk&#xff0c;发生unlink从比malloc要求大的largebin中取chunk&#xff0c;发生unlink _int_free free之后&#xff0c;与前后空闲的chunk…

认识CUDA

1 基本概念 1.1 什么是CUDA&#xff1f; CUDA(ComputeUnified Device Architecture)&#xff0c;是显卡厂商NVIDIA推出的运算平台。 CUDA是一种由NVIDIA推出的通用并行计算架构&#xff0c;该架构使GPU能够解决复杂的计算问题。 CUDA&#xff08;Compute Unified Device Arc…

copilot插件全解

COPILOT是一个基于AI的编程辅助工具&#xff0c;它可以帮助程序员自动编写代码&#xff0c;提高开发效率。COPILOT的插件主要是为了将其功能集成到不同的编程环境中&#xff0c;方便程序员使用。 目前&#xff0c;COPILOT支持多种编程环境&#xff0c;包括Visual Studio Code、…

stable diffusion 基础教程-图生图

界面 图生图大概有以下几个功能: 图生图涂鸦绘制局部绘制局部绘制(涂鸦蒙版)其常用的也就上面四个,接下来逐步讲解。 以图反推提示词 图生图可以根据反推提示词来获取相应图片的提示词,目前3种主流方式,如下: CLIP反推提示词:推导出的文本倾向于自然语言的描述方式,…

支持下载和阅读的漫画管理工具Teemii

什么是 Teemii &#xff1f; Teemii 是一款专为狂热漫画读者设计的精简 Web 应用程序。它为阅读和管理漫画集提供了一个简单而高效的平台。主要功能包括跨平台访问、浏览器内阅读、强大的元数据聚合器以及馆藏自动更新。Teemii 是专为那些寻求更加个性化和自主的方法来管理漫画…

论文管理器

论文管理器 这个论文管理器仍然存在许多漏洞。目前&#xff0c;通过按照一些例行程序操作&#xff0c;它可以正常工作。我将在有时间的时候改进代码&#xff0c;提供详细说明&#xff0c;并添加新功能。当该管理器的代码进行优化后&#xff0c;我会上传到github上。 一个建立…

C练习——定期存取并行

题目&#xff1a;假设银行一年整存零取的月息为1.875%&#xff0c;现在某人手头有一笔钱&#xff0c;他打算在今后5年 中&#xff0c;每年年底取出1000元作为孩子来年的教育金&#xff0c;到第5年孩子毕业时刚好取完这笔钱&#xff0c;请编 程计算第1年年初时他应存入银行多少钱…

接口自动化--断言

目标&#xff1a; 1、学习常见的自动化断言方法 2、把自动化断言分装和应用于接口测试 具体内容&#xff1a; 1、学习常见的自动化断言方法 第一类&#xff1a;比较大小和是否相等 而assert可以使用直接使用“”、“!”、“<”、“>”、“>”、"<"…