数据结构之树,实现堆的增删改查接口及堆的应用

news2024/9/29 7:20:12

目录

一、树

1.树的概念及结构

1.1树的概念

1.2树的相关概念

1.3树的表示

2.二叉树的概念及结构

2.1二叉树的概念

2.2特殊的二叉树

2.3二叉树的性质

2.4二叉树的存储结构

3.二叉树的顺序结构及实现

3.1二叉树的顺序结构

3.2堆的概念及结构

二、堆的实现

0.定义堆

1.初始化堆

2.堆的创建

3.堆的删除

4.堆的判空问题

5.堆的大小

6.堆的销毁

三、堆的应用

1.堆排序

2.topK问题

总结


一、树

1.树的概念及结构

1.1树的概念

树是一种非线性的数据结构,它是由N(N>=0)个有限节点组成一个具有层次关系的集合,叫它为树是因为它看起来像一颗倒挂的树,根朝上,叶子朝下

  • 树有一个特殊的节点,称为根节点,根节点没有前驱节点
  • 除了根节点外,其余节点被分为M个互不相交的集合,其中每一个集合又是一颗结构与树类似的子树,每棵子树的根节点有且只有一个前驱,可以有0或多个后继,因此树是递归定义的。

1.2树的相关概念

  • 节点的度:一个节点含有的子树的个数称为该节点的度
  • 叶节点或终端节点:度为0的节点称为叶子节点
  • 树的度:一个树中,最大的节点的度称为树的度

1.3树的表示

树需要保存值域,也需要保存节点和节点之间的关系,其中树有很多表示方法,如双亲表示法,孩子表示法,双亲孩子表示法以及孩子兄弟表示法,常用孩子兄弟表示法

typedef int DataType;
struct Node
{
    struct Node * child;
    struct Node * brother;
    DataType data;
};

2.二叉树的概念及结构

2.1二叉树的概念

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

 从上图可以看出,二叉树不存在度大于2的节点;二叉树有左右之分,次序不能颠倒,因此二叉树是有序树

2.2特殊的二叉树

  1. 满二叉树:一个二叉树,如果每次的节点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为k,且节点总数为2^K-1,则它就是满二叉树
  2. 完全二叉树:完全二叉树是效率很高的数据结构,对于深度为K的有N个节点的二叉树,当且仅当每个节点都与深度为K的满二叉树从1到N的节点一一对应,就成为完全二叉树

2.3二叉树的性质

  • 若规定根节点为第一层,则一棵非空二叉树的第i层上最多有2^(i-1)个节点
  • 若规定根节点为第一层,则深度为h的二叉树的最大节点数是2^h-1
  • 对于任意一棵二叉树,如果度为0的叶子节点个数为N0,度为2的分支节点为N2,N0 = N2+1
  • 若规定根节点的层数为1,具有n个节点的满二叉树的深度,h=log2(n+1)
  • 对于一个具有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 BinaryTreeNode * leftchild;
    struct BinaryTreeNode * rightchild;
    BTDataType    data;
};


//三叉链
struct BinaryTreeNode
{
    struct BinaryTreeNode *parent;
    struct BinaryTreeNode * leftchild;
    struct BinaryTreeNode * rightchild;
};

3.二叉树的顺序结构及实现

3.1二叉树的顺序结构

普通的二叉树一般不使用数组存储,因为会造成大量的空间浪费,完全二叉树适合顺序存储。

3.2堆的概念及结构

如果有一个关键码的集合K={k0,k1,k2,k3...kN-1},把它所有的元素按完全二叉树的顺序存储方式存储到一维数组中,并满足:Ki<=K2i+1,且Ki<=K2i+2(或者小于)则称为小堆,或者大堆,将根节点最大的堆叫做最大堆或者大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

      堆中某个节点的值总是不大于或不小于父节点的值;

       堆总是一颗完全二叉树

二、堆的实现

0.定义堆

堆的逻辑结构是一棵完全二叉树,物理结构为一个数组,要想实现堆的插入删除,需要再定义size和capacity两个值,分别表示当前完全二叉树中存储的有效数据和完全二叉树的容量。

typedef int HPDataType;

typedef struct
{
    HPDataType * a;
    int size;
    int capacity;
}HP;

1.初始化堆

void HeapInit(HP * php)
{
    assert(php);
    php->a = NULL;
    php->size = php->capacity = 0;
}

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;
    while(child>0)  //直到比到根节点结束
    {
        if(a[child] < a[parent])   //如果要创建小堆 if(a[child] >a[parent])进行调整
        {
            Swap(&a[child],&a[parent]);  //传递的是数组的值,所以类型为HPDataType,传递的是一个地址,所以用一个指针接收,将child指向的值换为parent指向的值,下标没变
            child = parent; //向上走,更换下标
            parent = (child -1)/2;  //重新找到parent节点
        }
        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 = realloc(php->a,sizeof(HPDataType)*newCapacity);
        if(tmp ==NULL)
            exit(-1);
        php->a = tmp;
        php->capacity = newCapacity;
    }
    //先插入,再调整
    php->a[php->size] = x;
    php->size++;
    
    //如果默认建大堆,则插入的节点向上调整
    //向上调整的时候,调整的是数组的顺序
    //以及知道当前数据的下标
    
    AdjustUp(php->a,php->size-1);
}


//形参为定义的堆,要实现堆结构的数组,数组的个数
void HeapCreate(HP * php,HPDataType * a,int n)
{
    assert(php);
    HeapInit(php);
    for(int i = 0;i<n;i++)
    {
        //依次插入,插入到哪个堆,以及数据
        HeapPush(php,a[i]);
    }
}

3.堆的删除

同堆的插入问题,删除节点后需要仍然保持堆的有序问题,对于顺序表来说,头删时间复杂度为O(N),尾删时间复杂度为O(1),所以删除堆顶数据时,先和最后一个交换顺序,删除表尾的数据,然后再根据为大堆或小堆,进行调整堆的顺序。


void AdjustDown(HPDataType * a, int n, int parent)
{
    int child = 2*parent +1;
    while()
    {
        //此时可能左孩子大,也可能右孩子大,需要和大的交换,上面定义左孩子
        //也有可能没有右孩子,如果没有就不++
        if(a[child] <a[child+1]  && child+1 <n)
        {
            ++child;
        }
        
       //向下交换
        if(a[parent] <a[child])    //此时,如果要调整为小堆 箭头 > 
        {
            Swap(&a[parent],&a[child]);
            parent = child;
            child = 2*parent +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);
}

4.堆的判空问题

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

5.堆的大小

int HeapSize(HP * php)
{
    assert(php);
    return php->size;
  }

6.堆的销毁

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

三、堆的应用

1.堆排序

堆排序即利用堆的思想进行排序,总共分为两个步骤:

  1. 建堆 升序 建大堆 降序 建小堆
  2. 利用堆删除思想进行排序

2.topK问题

      即求数据中前K个最大的元素或者最小的元素,一般数据量较大。对于topK问题,最简单的就是排序,但是数据量非常大的时候,排序不可取,最佳方式用堆解决,基本思路如下:

       前K个最大的元素,建立大堆

       前K个最小的元素,建立小堆

HPDataType HeapPop(HP * php)
{
    asser(php);
    assert(php->size >0);
    return php->a[0];
}


void TestTopK()
{
    //根据现有的数据建立堆 建立大或小在push中定义
    int a[] = {27,15,19,18,28,34,65,49,25,37};
    HP hp;
    HeapInit(&hp);
    for(int i = 0; i<sizeof(a)/sizeof(int);i++)
    {
        HeapPush(hp,a[i]);
    }
    //取前5个
    int k = 5;
    while(k--)
    {
        printf("%d" ,HeapTop(&hp);
        HeapPop(&hp);
    }
    HeapDestroy(&hp);
}

 


总结

本文主要介绍了树的概念及性质,堆的概念及增删改查的接口,堆的应用topK问题,技术有限,如有错误请指正。

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

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

相关文章

神经网络:Zero2Hero 2 - MLP、Embedding

Zero → \to → Hero : 2 接上篇&#xff0c;Zero → \to → Hero : 1&#xff0c;进一步的扩展模型&#xff1a; 增加输入字符序列的长度&#xff0c;通过多个字符预测下一个字符的概率分布增加模型的深度&#xff0c;通过多层的MLP来学习和预测字符的生成概率增加嵌入层&…

LeetCode高频算法刷题记录5

文章目录 1. 最长递增子序列【中等】1.1 题目描述1.2 解题思路1.3 代码实现 2. 接雨水【困难】2.1 题目描述2.2 解题思路2.3 代码实现 3. 二叉树中的最大路径和【困难】3.1 题目描述3.2 解题思路3.3 代码实现 4. 二叉树的中序遍历【简单】4.1 题目描述4.2 解题思路4.3 代码实现…

HTB靶机014-Sunday-WP

Sunday 靶机IP&#xff1a;10.10.10.76 PortScan Nmap 快速扫描&#xff1a; ┌──(xavier㉿kali)-[~] └─$ sudo nmap -sSV -T4 -F 10.10.10.76 Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-06 00:10 CST Nmap scan report for 10.10.10.76 Host is …

2023年广东省中职网络安全Web渗透测试解析(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 1.访问地址http://靶机IP/task1,分析页面内容,获取flag值,Flag格式为flag{xxx}; 2.访问地址http://靶机IP/task2,访问登录页面。用户user01的密码为1-1000以内的数,获取用户user01的密码,将密码作为Flag进行提交,Flag格式…

客户端读取响应头+后端读取请求头的那些事

在一些特殊场景中&#xff0c;我们在客户端想要去获取服务端接口设置的一些自定义响应头&#xff0c;服务端该如何处理&#xff0c;客户端才能取到这些自定义响应头的值呢&#xff1f; 特殊场景&#xff0c;我这里也举例一下&#xff0c;原生页面webView嵌入web页面。这个时候…

Shell脚本编程入门--Day2

文章目录 几个简单内置shell命令shell字串的语法计算变量长度的各种玩法批量修改文件名特殊shell扩展变量实际应用父子shell创建进程列表&#xff08;创建子shell&#xff09; 几个简单内置shell命令 echo -n 不换行输出 -e 解析字符串中的特殊符号 &#xff08;\n, \r, \t, \…

浏览器的进程和线程

浏览器是多进程多线程的应用程序 浏览器进程 主要负责界面显示、用户交互、子进程管理等。浏览器进程内部会启动多个线程处理不同的任务。 网络进程 负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务。 渲染进程 渲染进程启动后&#xff0c;会开启一个染主线…

macOS 13.4正式版(22F66)With OpenCore 0.9.2开发版 and winPE双引导分区原版镜像

镜像特点 原文地址&#xff1a;http://www.imacosx.cn/113625.html 完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引导分区&#xff08;如有需要&#xff0c;可以自行直…

java boot项目基础配置之设置启动端口

因为 springboot 项目是一个内嵌的tomcat 那么 我们就来研究一下 怎么改它的启动端口 其实 它的配置 还是非常多的 我们基础部分讲一下 后面 到实用部分 再一边用 一边再看一些 首先 我们如果不设置 他就会占用 我们的 8080端口 那么 我们最好就直接用 80端口 就不用输入端口…

【滤波】卡尔曼滤波数学

本文主要翻译自rlabbe/Kalman-and-Bayesian-Filters-in-Python的第7章节07-Kalman-Filter-Math&#xff08;卡尔曼滤波数学&#xff09;。 %matplotlib inline#format the book import book_format book_format.set_style()简介 如果你已经学习到了这一步&#xff0c;我希望你…

vue基础知识一:说说你对vue的理解?

一、从历史说起 Web是World Wide Web的简称&#xff0c;中文译为万维网 我们可以将它规划成如下的几个时代来进行理解 石器时代 文明时代 工业革命时代 百花齐放时代 石器时代 石器时代指的就是我们的静态网页&#xff0c;可以欣赏一下1997的Apple官网 最早的网页是没有数据库…

day1 IO 模型

目录 基本概念 同步和异步 阻塞和非阻塞 线程在运行过程中&#xff0c;可能由于以下几种原因进入阻塞状态&#xff1a; 可能阻塞套接字的Linux Sockets API调用分为以下四种 五种 I/O 模型 阻塞I/O 非阻塞I/O ​编辑 I/O多路复用模型 信号驱动式I/O模型 异步I/O 模型 …

网络模块封装

网络模块封装 library-network模块配置依赖一.自定义LiveDataCallAdapterFactory1.定义ApiResponse返回的数据类型2.LiveDataCallAdapter.kt3.LiveDataCallAdapter.kt 二.自定义CustomGsonConverterFactory三.拦截器1.HeaderInterceptor请求头拦截器2.BasicParamsInterceptor参…

Android - 内容提供者(Content Provider) 使用

Android - 内容提供者(Content Provider) 内容提供者组件通过请求从一个应用程序向其他的应用程序提供数据。这些请求由类 ContentResolver 的方法来处理。内容提供者可以使用不同的方式来存储数据。数据可以被存放在数据库&#xff0c;文件&#xff0c;甚至是网络。 有时候需…

Python如何进行性能测试?(Locust对接口进行压测)

python如何进行性能测试呢&#xff1f;其实原理就是对于接口进行加线程&#xff0c;打个比方就是当你有一个电梯&#xff0c;你同时可以搭载多少个人坐电梯那这个人数就是这部电梯的其中一个性能指标&#xff0c;那么对于接口来说每秒钟能有多少人成功发起请求后得到成功的响应…

QT 学习笔记2 信号与槽

上次做的界面&#xff0c;并没有逻辑。你点击按钮&#xff0c;并不会执行什么 要想其能作出反映&#xff0c;就不得不提到一个很重要的机制---信号与槽 当我们点击确定的时候&#xff0c;按钮会发出一个信号 点击确定的时候&#xff0c;会执行一段代码&#xff0c;这段程序就…

Ada 语言学习(3)复合类型数据——Array

文章目录 Array数据类型声明数组索引数组范围数组复制数组初始化直接赋值通过拷贝赋值不同索引范围但长度相等非指定类型边界收缩 多维数组数组遍历数组切片访问和动态检查直接访问动态检查 数组字面量 Array literal数组拼接两个数组拼接数组和单个值拼接 Array Equality&…

机器学习平台 PAI 支持抢占型实例,模型服务最高降本 90%

助力模型推理服务降本增效&#xff0c;适用于推理成本敏感场景&#xff0c;如&#xff1a;AIGC 内容生成异步推理、批量图像处理、批量音视频处理等。 在 AI 开发及服务不断追求效率的背景下&#xff0c;阿里云机器学习平台 PAI 宣布支持抢占型实例&#xff08;Spot Instance&a…

2023逆向分析代码渗透测试flag0072解析(超详细)

一、竞赛时间 180分钟 共计3小时 1.从靶机服务器的FTP上下载flag0072,分析该文件,请提交代码保护技术的类型。提交格式:XXXX。 2.提交被保护的代码所在地址。提交格式: 0xXXXX。 3.提交代码解密的密钥。提交格式: 0xXX。 4.请提交输入正确flag时的输出。提交格式: XXXX。…

Python入门(十二)while循环(二)

while循环&#xff08;二&#xff09; 1.使用while循环处理列表和字典2.在列表之间移动元素3.删除为特定值的所有列表元素4.使用用户输入来填充字典 作者&#xff1a;xiou 1.使用while循环处理列表和字典 到目前为止&#xff0c;我们每次都只处理了一项用户信息&#xff1a;获…