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

在这里插入图片描述

🐇

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

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

文章目录

  • 前言
  • 📚一、二叉树链式结构的接口补充
    • 📔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是否小于0if (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.rhkb.cn/news/145831.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自学WEB后端05-Node.js后端服务链接数据库redis

嘿&#xff0c;亲爱的小伙伴们&#xff01;&#x1f604; 今天我要给大家分享一个超级方便且高效的 NoSQL 类型数据库——Redis&#xff01;&#x1f4a1; 它可不是一般的关系型数据库哦&#xff0c;而是以键值对形式存储数据的内存数据库。&#x1f4da; 快跟着我一起来学习如…

使用VSCODE 调试ros2具体设置

vscode 调试 ROS2 张得帅&#xff01; 于 2023-09-09 15:39:39 发布 456 收藏 1 文章标签&#xff1a; vscode ros2 版权 1、在下列目录同层级找到.vscode文件夹 . ├── build ├── install ├── log └── src 2、 安装ros插件 3、创建tasks.json文件&#xff0c;添…

开绕组电机零序Bakc EMF-based无感控制以及正交锁相环inverse Park-based

前言 最近看论文遇到了基于反Park变换的锁相环&#xff0c;用于从开绕组永磁同步电机零序电压信号中提取转子速度与位置信息&#xff0c;实现无感控制。在此记录 基于零序Back EMF的转子估算 开绕组电机的零序反电动势 e 0 − 3 ω e ψ 0 s i n 3 θ e e_0-3\omega_e\psi_…

day06_循环

今日内容 零、 复习昨日 一、循环 二、流程控制关键词 零、 复习昨日 8个基本数据类型 变量的使用步骤 1)声明2)赋值3)使用 声明,数据类型 变量名 不一定非得是基本类型 int a; String s; Scanner scanner;赋值,只要符合类型(能默认转换)就能赋值 int a 1; double d 1; Scann…

国庆加速度!新增功能点锁定功能,敏捷开发新增估算功能,助力项目快速突破!

大家好&#xff0c;CoCode开发云旗下Co-Project V3.6智能项目管理平台正式发布&#xff0c;平台新增功能点锁定功能、敏捷开发模式新增估算板块和两种估算方式。 功能点锁定功能进一步提高了项目估算的灵活性和准确性&#xff0c;有利于提高项目估算效率&#xff1b;而敏捷开发…

RTSP协议抓包及讲解

文章目录 前言一、RTSP 亲手搭建直播点播1、数据源为视频文件2、数据源为摄像头①、搭建 RTSP 流媒体服务器②、客户端拉流 二、RTSP 协议简介三、手撕 RTSP 协议1、Wireshark 抓包①、搭建环境②、wireshark 抓包 2、RTSP 交互流程①、OPTIONS②、DESCRIBE③、SETUP④、PLAY⑤…

Acwing 143. 最大异或对

Acwing 143. 最大异或对 题目描述思路讲解代码展示 题目描述 思路讲解 这道题的启示是&#xff1a;字典树不单单可以高效存储和查找字符串集合,还可以存储二进制数字 思路:将每个数以二进制方式存入字典树,找的时候从最高位去找有无该位的异. 代码展示 #include<iostream…

Spring的依赖注入(DI)以及优缺点

Spring的依赖注入&#xff08;DI&#xff09;&#xff1a;解释和优点 依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff09;是Spring框架的核心概念之一&#xff0c;也是现代Java应用程序开发的重要组成部分。本文将深入探讨DI是什么&#xff0c;以及它的…

编程每日一练(多语言实现)基础篇:满足abcd=(ab+cd)^2的数 (增加Go语言实现)

文章目录 一、实例描述二、技术要点三、代码实现3.1 C 语言实现3.2 Python 语言实现3.3 Java 语言实现3.4 JavaScript 语言实现3.5 Go 语言实现 一、实例描述 假设 abcd 是一个四位整数&#xff0c;将它分成两段&#xff0c;即 ab 和 cd&#xff0c;使之相加求和后再平方。求满…

WinHex数据恢复方法(误删后没覆盖)

winhex永远滴神&#xff01;winhex永远滴神&#xff01;winhex永远滴神&#xff01; md&#xff0c;安卓手机插u盘&#xff0c;改个文件夹名竟然把整个文件夹搞没了&#xff01;于是我赶紧查怎么恢复&#xff0c;然后依次找到并试用了diskgenus&#xff08;410RMB&#xff09;、…

Blued引流脚本

于多数人来说&#xff0c;引流都是一个比较困难的操作&#xff0c;因为流量不会听你的。所以任何人在网上做生意&#xff0c;或者开一个实体店&#xff0c;都会为流量而发愁&#xff0c;其实对于流量的吸引来说&#xff0c;我们越是刻意为之&#xff0c;可能所获得的效果也越不…

实现单行/多行文本溢出

在日常开发展示页面&#xff0c;如果一段文本的数量过长&#xff0c;受制于元素宽度的因素&#xff0c;有可能不能完全显示&#xff0c;为了提高用户的使用体验&#xff0c;这个时候就需要我们把溢出的文本显示成省略号。 一. 单行文本溢出 即文本在一行内显示&#xff0c;超出…

网络安全渗透测试工具之skipfish

网络安全渗透测试工具skipfish介绍 在数字化的时代,Web 应用程序安全成为了首要任务。想象一下,您是一位勇敢的安全冒险家,迎接着那些隐藏在 Web 应用程序中的未知风险。而在这个冒险之旅中,您需要一款强大的工具来帮助您发现漏洞,揭示弱点。而这个工具就是 Skipfish。 …

ChatGPT Prompting开发实战(十二)

一、如何开发prompts实现个性化的对话方式 通过设置“system”和“user”等roles&#xff0c;可以实现个性化的对话方式&#xff0c;并且可以结合参数“temperature”的设定来差异化LLM的输出内容。在此基础上&#xff0c;通过构建一个餐馆订餐对话机器人来具体演示对话过程。…

计算机竞赛 深度学习卷积神经网络垃圾分类系统 - 深度学习 神经网络 图像识别 垃圾分类 算法 小程序

文章目录 0 简介1 背景意义2 数据集3 数据探索4 数据增广(数据集补充)5 垃圾图像分类5.1 迁移学习5.1.1 什么是迁移学习&#xff1f;5.1.2 为什么要迁移学习&#xff1f; 5.2 模型选择5.3 训练环境5.3.1 硬件配置5.3.2 软件配置 5.4 训练过程5.5 模型分类效果(PC端) 6 构建垃圾…

国庆作业2

select实现服务器并发 代码&#xff1a; #include <myhead.h>#define ERR_MSG(msg) do{\printf("%d\n",__LINE__);\perror(msg);\ }while(0)#define PORT 8888#define IP "192.168.1.5"int main(int argc, const char *argv[]) {//创建流式套接字…

1.5.C++项目:仿muduo库实现并发服务器之socket模块的设计

项目完整版在&#xff1a; 一、socket模块&#xff1a;套接字模块 二、提供的功能 Socket模块是对套接字操作封装的一个模块&#xff0c;主要实现的socket的各项操作。 socket 模块&#xff1a;套接字的功能 创建套接字 绑定地址信息 开始监听 向服务器发起连接 获取新连接 …

CVE-2023-5129 libwebp堆缓冲区溢出漏洞影响分析

漏洞简述 近日苹果、谷歌、Mozilla和微软等公司积极修复了libwebp组件中的缓冲区溢出漏洞&#xff0c;相关时间线如下&#xff1a; 9月7日&#xff0c;苹果发布紧急更新&#xff0c;修复了此前由多伦多大学公民实验室报告的iMessage 0-click 漏洞&#xff0c;漏洞被认为已经被…

华为云云耀云服务器L实例评测 | 实例场景体验之搭建个人博客:通过华为云云耀云服务器构建个人博客

华为云云耀云服务器L实例评测 &#xff5c; 实例场景体验之搭建个人博客&#xff1a;通过华为云云耀云服务器构建个人博客 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀…

idea Springboot在线商城系统VS开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 在线商城系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有 完整的源代码和数据库&…