【数据结构】二叉树的顺序结构实现及时间复杂度计算(二)

目录

一,二叉树的顺序结构实现

        1,二叉树的顺序结构

        2,堆的概念及结构

        3,堆的接口实现

1,堆的创建

2,接口函数

3,初始化

4,销毁

5,是否增容

6,交换数据

7,堆向上调整算法

8,插入数据

9,删除数据

10,堆向下调整算法

11,打印数据

12,取堆顶元素

13,判空

14,数据个数

        4,源代码

1,Heap.h

2,Heap.c

二,建堆的时间复杂度

        1,堆的创建

1,向上调整建堆法:

2,向下调整建堆法

        2,向上调整建堆的时间复杂度

        3,向下调整建堆的时间复杂度

三,堆的应用

        1,堆排序

1,建堆

2,利用堆交换删除思想来进行排序

        2,TOP-K问题


一,二叉树的顺序结构实现

        1,二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储

二叉树的顺序储存结构就是用一堆数组储存二叉树中的结点,并且结点的储存位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等;

        2,堆的概念及结构

现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段;

如果有一个关键码的集合K = {k0 ,k1 ,k2 ,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足父亲结点的值大于等于或者小于等于子孙结点的值,则称为大堆(或小堆)

将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆;

堆的性质:

1,堆中某个结点的值总是不大于或不小于其父结点的值;

2,堆总是一棵完全二叉树

        3,堆的接口实现

1,堆的创建

//堆(二叉树)的基本顺序结构
//动态顺序表
typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;

首先创建一个结构体表示顺序表,这里我们将数据类型重命名为HPDataType,便于我们后续对顺序表数据类型的修改;

a为类型指针代表一维数组

size为有效数据个数,capacity储存数据的最大容量值

2,接口函数

// 小根堆
// 堆初始化
void HPInit(HP* php);
// 销毁
void HPDestroy(HP* php);
// 是否增容
void HPCapacity(HP* php);
// 交换数据
void swap(HPDataType* x, HPDataType* y);
// 向上调整
void AdJustUp(HPDataType* a, int size);
// 插入数据
void HPPush(HP* php, HPDataType x);
// 向下调整
void AdJustDown(HPDataType* a, int size, int parent);
// 删除数据
void HPPop(HP* php);
// 打印数据
void HPPrint(HP* php);
// 取堆顶元素
HPDataType HPTop(HP* php);
// 判空
int HeapEmpty(HP* php);
// 数据个数
int HPSize(HP* php);

这里我们实现小根堆大小根堆的实现原理都一样;

以上是要实现的接口函数;

3,初始化

	//定义HP hp;//初始化HPInit(&hp);
// 堆的初始化
void HPInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);php->size = 0;php->capacity = 4;
}

首先要进行断言php不能为空,如果php为空则下面对php解引用就会报错;

刚开始先给 a 申请4个HPDataType大小的空间,这个自己可以任意修改,然后对sizecapacity进行赋值;

易错点:给指针a申请的是HPDataType大小的空间而不是HP大小的空间,这里由于学习过链表的缘故就容易搞混淆;

4,销毁

// 堆的销毁
void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

然后就是销毁顺序表,直接用 free 释放掉 a 即可,再置为空指针,再重置 size 和 capacity ;

5,是否增容

//是否增容
void HPCapacity(HP* php)
{assert(php);if (php->size == php->capacity){php->a = (HPDataType*)realloc(php->a,sizeof(HPDataType)*(php->capacity * 2));if (php->a == NULL){perror("realloc");exit(-1);}php->capacity *= 2;}
}

像后面如果进行数据插入的话,就需要检查空间是否需要增容了,也很好判断,当size等于capacity时就需要增容了,我们这里是选择直接扩容一倍;

然后再更新一下capacity的值就行了;

6,交换数据

//交换数据
void swap(HPDataType* x, HPDataType* y)
{assert(x&&y);HPDataType z = *x;*x = *y;*y = z;
}

老样子先对指针xy进行断言,然后定义z完成xy的值交换,交换数据后续会使用到;

7,堆向上调整算法

//向上调整
void AdJustUp(HPDataType* a,int size)
{assert(a);int child = size-1;while (child>0){int parent = (child - 1) / 2;//孩子与父亲比较值if (a[child] < a[parent]){//交换swap(&a[child], &a[parent]);child = parent;}else{break;}}
}

先断言,当向堆中插入数据数据需要与父亲结点进行比较调整

我们传入指针a,数据个数sizesize用来确定插入数据的下标

然后建立循环遍历,父亲(parent)结点的下标为孩子(child)结点的下标减一除以二:

parent=(child -1)/ 2

a[child]的值小于a[parent]的值则进行交换,此时parent的身份转变为child继续向上调整,当child小于等于0,此时已到顶点无需再进行调整,退出循环;

8,插入数据

//插入数据
void HPPush(HP* php, HPDataType x)
{assert(php);//是否增容HPCapacity(php);php->a[php->size] = x;php->size++;//向上调整AdJustUp(php->a, php->size);
}

首先判断是否需要增容,此时size的值就是末尾的数的下标加一

直接对其下标进行赋值,再让size加一

然后就需要将新数据向上调整,相当于更新维护堆结构

9,删除数据

//删除数据
void HPPop(HP* php)
{assert(php);assert(php->size > 0);//交换数据值swap(&php->a[php->size - 1], &php->a[0]);php->size--;//向下调整AdJustDown(php->a, php->size,0);
}

删除堆顶数据;

我们要删除数据不能直接删除否则会破坏堆结构,重新调整代价太大(时间复杂度太大);

我们可以将插入的数据与堆顶的数据进行交换,然后再让size--相当于删除了堆顶数据,然后再让堆顶的数据向下进行调整,以此来更新维护堆结构;

10,堆向下调整算法

//向下调整
void AdJustDown(HPDataType* a, int size,int parent)
{assert(a);int child = parent * 2 + 1;while (child<size){//左孩子与右孩子比较值if (child+1<size && a[child]>a[child + 1]){child++;}//孩子与父亲比较值if (a[child] < a[parent]){//交换swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

孩子(child) 的下标等于父亲(parent) 下标×2+1:child=parent*2+1

建立循环遍历数组,先令左孩子为要与父亲比较的孩子,然后如果右孩子存在再让左孩子与右孩子进行比较,选出值小的孩子;

然后让孩子与父亲进行比较,如果孩子小于父亲则进行值的交换,此时孩子的身份变成父亲,继续循环;

当孩子的值大于等于size时循环结束,或者当孩子大于等于父亲则跳出循环;

11,打印数据

//打印数据
void HPPrint(HP* php)
{assert(php);int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->a[i]);}
}

堆是个顺序表;

size是数据个数,然后进行遍历打印即可;

12,取堆顶元素

//取堆顶元素
HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}

直接返回堆顶的数据即可

13,判空

//判空
int HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}

直接返回 size是否等于0的判断结果即可,如size等于0则为真,不等于0则为假;

14,数据个数

//数据个数
int HPSize(HP* php)
{assert(php);return php->size;
}

直接返回size即可;

        

        4,源代码

1,Heap.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>//堆(二叉树)的基本顺序结构
typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;// 小根堆// 堆初始化
void HPInit(HP* php);
// 销毁
void HPDestroy(HP* php);
// 是否增容
void HPCapacity(HP* php);
// 交换数据
void swap(HPDataType* x, HPDataType* y);
// 向上调整
void AdJustUp(HPDataType* a, int size);
// 插入数据
void HPPush(HP* php, HPDataType x);
// 向下调整
void AdJustDown(HPDataType* a, int size, int parent);
// 删除数据
void HPPop(HP* php);
// 打印数据
void HPPrint(HP* php);
// 取堆顶元素
HPDataType HPTop(HP* php);
// 判空
int HeapEmpty(HP* php);
// 数据个数
int HPSize(HP* php);

2,Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"//堆初始化
void HPInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);php->size = 0;php->capacity = 4;
}
//销毁
void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}
//交换数据
void swap(HPDataType* x, HPDataType* y)
{assert(x&&y);HPDataType z = *x;*x = *y;*y = z;
}
//向上调整
void AdJustUp(HPDataType* a,int size)
{assert(a);int child = size-1;while (child>0){int parent = (child - 1) / 2;if (a[child] < a[parent]){swap(&a[child], &a[parent]);child = parent;}else{break;}}
}
//是否增容
void HPCapacity(HP* php)
{assert(php);if (php->size == php->capacity){php->a = (HPDataType*)realloc(php->a,sizeof(HPDataType)*(php->capacity * 2));if (php->a == NULL){perror("realloc");exit(-1);}php->capacity *= 2;}
}
//插入数据
void HPPush(HP* php, HPDataType x)
{assert(php);//是否增容HPCapacity(php);php->a[php->size] = x;php->size++;AdJustUp(php->a, php->size);
}//向下调整
void AdJustDown(HPDataType* a, int size,int parent)
{assert(a);int child = parent * 2 + 1;while (child<size){//左孩子与右孩子比较值if (child+1<size && a[child]>a[child + 1]){child++;}//孩子与父亲比较值if (a[child] < a[parent]){//交换swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
//删除数据
void HPPop(HP* php)
{assert(php);assert(php->size > 0);swap(&php->a[php->size - 1], &php->a[0]);php->size--;AdJustDown(php->a, php->size,0);
}
//打印数据
void HPPrint(HP* php)
{assert(php);int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->a[i]);}
}
//取堆顶元素
HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}//判空
int HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}//数据个数
int HPSize(HP* php)
{assert(php);return php->size;
}

二,建堆的时间复杂度

        1,堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆根节点左右子树不是堆,我们怎么调整呢?

这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆

int a[] = { 7,8,3,5,1,9,5,4 };
HPSort(a, sizeof(a) / sizeof(a[0]));

1,向上调整建堆法:

//建堆--向上调整建堆 
int num = n;
for (int i = 1; i <= num; i++)
{//向上调整AdJustUp(a,i);
}

直接一直循环插入数据即可;

2,向下调整建堆法

//建堆--向下调整建堆  O(N)
int num = n;
for (int i = (num - 1 - 1) / 2;i >= 0; i--)
{//向下调整AdJustDown(a,n,i);
}

需要先把根结点下面的子树都搞成堆,所以先从倒数的第一个非叶子节点的子树开始向下调整,然后循环遍历让结点逐渐缩减(逐渐往上走)向下调整,如图所示;

        2,向上调整建堆的时间复杂度

  F(h)=2^1*1+2^2*2+...+2^(h-2)*(h-2)+2^(h-1)*(h-1)

2*F(h)=2^2*1+2^3*2+...+2^(h-1)*(h-2)+2^h*(h-1)

两式错位相减:

F(h)= - 2^1-2^2-2^3-...-2^(h-2)-2^(h-1)+2^h*(h-1)

F(h)= - 2^h+2-2^h+2^h*h

F(h)=2^h(h-2)+2

假设树有N个结点:2^h-1=N      h=log(N+1)

F(N)=(N+1)*(log(N+1)-2) + 2 ≈ N*logN

所以用向上调整建堆的时间复杂度为O(N*logN);

        3,向下调整建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

则需要移动结点总的移动步数为:

T(n) = 2^0*(h-1)+2^1*(h-2)+2^2*(h-3)+2^3*(h-4)+...+2^(h-3)*2+2^(h-2)*1

2*T(n)=2^1*(h-1)+2^2*(h-2)+2^3*(h-3)+2^4*(h-4)+...+2^(h-2)*2+2^(h-1)*1

两式错位相减:

T(n)=1-h+2^1+2^2+2^3+2^4+...+2^(h-2)+2^(h-1)

T(n)=2^h-1-h

n=2^h-1  ==  h=log(n+1)

T(n)=n-log2(n+1)≈n

所以用向下调整建堆的时间复杂度为O(N);

由此可见,建堆的时间复杂度为O(N)!

三,堆的应用

        1,堆排序

1,建堆

升序:建大堆

降序:建小堆

2,利用堆交换删除思想来进行排序

int num = n;
while (num>1)
{//交换数据swap(&a[0], &a[num-1]);num--;//向下调整AdJustDown(a,num,0);
}

其实很简单,比如我们要整一个升序数组,有人会说建小堆,其实这是错的;

如果建小堆:堆顶的数不变,找次大的数要将堆顶以下的数全部重新排序,这不现实,所以pas;

建大堆:堆顶的数据与末尾的数据交换,然后令num--(将末尾的数隔离开),再将堆顶的数向下调整,然后循环遍历一直找次大的数,当num等于1时堆顶的数为最小值,此时的数组便是升序数组!

这就是堆的交换删除思想,这个循环的时间复杂度为O(N*logN)

        2,TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决;

正常思路:

把这个N建成大堆,PopK次,即可找出最大的前K

但是,当N非常大时,假设N时10亿,那就是10亿个整数,所需的空间太大了,不可取!

优化思路:

1,将前K个数建成小堆

2,后面将N-K个数依次与堆顶的数据进行比较,如比堆顶的数据大,则替换他为堆顶,然后向下调整

3,最后比较完了,这个小堆的值就是最大的前K个

第二阶段就到这里了,下面更新二叉树的链式结构的实现;

如有不足之处欢迎来补充交流!

完结。。。


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

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

相关文章

适用于Linux的Windows子系统(PHP搭建lmap、redis、swoole环境)

目录 前言 一、Windows安装Linux子系统 二、Ubuntu搭建PHP开发环境 1.PHP 安装 2.Apache2 安装 3.MySQL安装 4.Redis安装 5.Swoole安装 总结 前言 系列分为三章&#xff08;从安装到项目使用&#xff09;&#xff1a; 一、适用于Linux的Windows子系统&#xff08;系统安装步骤…

springboot整合mybatis实现增删改查(xml)--项目阶段1

目录 一、前言 二、创建项目 创建MySQL数据库和表 创建springboot项目 本文总体代码结构图预览 三、编写代码 &#xff08;一&#xff09;新建实体层属性类 &#xff08;二&#xff09;新建数据层mapper接口 &#xff08;三&#xff09;新建mapper的映射SQL&#xff08…

项目实战:ES的增加数据和查询数据

文章目录 背景在ES中增加数据新建索引删除索引 在ES中查询数据查询数据总数量 项目具体使用&#xff08;实战&#xff09;引入依赖方式一&#xff1a;使用配置类连接对应的es服务器创建配置类编写业务逻辑----根据关键字查询相关的聊天内容在ES中插入数据 总结提升 背景 最近需…

安达发|APS排程系统解决各类制造业难题方案

APS(Advanced Product Scheduling,先进产品计划)软件是一种基于计算机技术的生产计划和调度系统&#xff0c;广泛应用于汽车制造、电子制造、注塑、化工、纺织等行业。本文将详细介绍APS软件在这些行业的应用场景及其优势。 一、汽车制造 1. 零部件生产计划&#xff1a;APS软件…

SVN基本使用笔记——广州云科

简介 SVN是什么? 代码版本管理工具 它能记住你每次的修改 查看所有的修改记录 恢复到任何历史版本 恢复己经删除的文件 SVN跟Git比&#xff0c;有什么优势 使用简单&#xff0c;上手快 目录级权限控制&#xff0c;企业安全必备 子目录Checkout&#xff0c;减少不必要的文件检出…

开机性能-如何抓取开机systrace

一、理论 1.背景 抓取开机 trace 需要使用 userdebug 版本&#xff0c;而我们测试开机性能问题时都要求使用 user 版本&#xff0c;否则会有性能损耗问题。因此想要在抓取开机性能trace 时&#xff0c;需要在 user 版本上打开 atrace 功能之后才能抓取 trace&#xff0c;默认 …

CRM软件系统能否监控手机的使用

CRM可以监控手机吗&#xff1f;答案是不可以。CRM是一款帮助企业优化业务流程&#xff0c;提高销售效率的工具。例如Zoho CRM&#xff0c;最多也就是听一下销售的通话录音&#xff0c;却不可以监控手机&#xff0c;毕竟CRM不是一款监控软件。 CRM的主要作用有以下几点&#xf…

【Linux】工具GCC G++编译器轻度使用(C++)

目录 一、关联知识背景 二、GCC如何的编译过程 【2.1】预处理(进行宏替换) 【2.2】编译(生成汇编) 【2.3】连接(生成可执行文件或库文件) 三、GCC命令的常用选项 四、动静态链接 一、关联知识背景 gcc 与 g 分别是 gnu 的 c & c 编译器 gcc/g 在执行编译工作的时候…

重建与发展:数字资产借贷行业朝着可持续发展迈进!

纵观历史&#xff0c;贷款和货币一样古老&#xff0c;无论哪种形式的货币都需要有其借贷市场。现在&#xff0c;比特币以其分散和透明的性质&#xff0c;在加密领域占据龙头地位。 就像之前的货币一样&#xff0c;比特币要真正蓬勃发展&#xff0c;也需要一个强大的借贷市场。然…

javaee spring aop实现事务 项目结构

spring配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:context"http://www.springframewo…

基于AERMOD模型在大气环境影响评价中的实践技术应用

随着我国经济快速发展&#xff0c;我国面临着日益严重的大气污染问题。近年来&#xff0c;严重的大气污染问题已经明显影响国计民生&#xff0c;引起政府、学界和人们越来越多的关注。大气污染是工农业生产、生活、交通、城市化等方面人为活动的综合结果&#xff0c;同时气象因…

数学建模--退火算法求解最值的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 """ 1.设定退火算法的基础参数 2.设定需要优化的函数,求解该函数的最小值/最大值 3.进行退火过程&#xff0c;随机产生退火解并且纠正,直到冷却 4.绘制可视化图片进行了解退火整体过程 &…

C# OpenVino Yolov8 Seg 分割

效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp;namespace OpenVino_Yolov8_Demo {public…

web请求cookie中expires总结

用意 cookie 有失效日期 "expires"&#xff0c;如果还没有过失效期&#xff0c;即使重新启动电脑&#xff0c;cookie 仍然不会丢失 注意&#xff1a;如果没有指定 expires 值&#xff0c;那么在关闭浏览器时&#xff0c;cookie 即失效。 设置 如果cookie存储时间大…

RHCE——十七、文本搜索工具-grep、正则表达式

RHCE 一、文本搜索工具--grep1、作用2、格式3、参数4、注意5、示例5.1 操作对象文件&#xff1a;/etc/passwd5.2 grep过滤命令示例 二、正则表达式1、概念2、基本正则表达式2.1 常见元字符2.2 POSIX字符类2.3 示例 3、扩展正则表达式3.1 概念3.2 示例 三、作业1、作业一2、作业…

华为云云耀云服务器L实例评测|用docker搭建frp服务测试

华为云云耀云服务器L实例评测&#xff5c;用docker搭建frp服务测试 0. 环境 华为云耀云L实例EulerOS 1. 安装docker 检查yum源&#xff0c;本EulerOS的源在这里&#xff1a; cd /etc/yum.repos.d 更新源 yum makecache 安装 yum install -y docker-engine 运行测试 d…

ATFX汇市:美初请失业金人数21.6万人,连降四期,劳动力供需偏紧

ATFX汇市&#xff1a;9月7日&#xff0c;美国劳工部数据显示&#xff1a;美国至9月2日当周初请失业金人数最新值21.6万人&#xff0c;低于前值22.9万人&#xff08;修正前22.8万人&#xff09;&#xff0c;低于预期值23.4万人。回顾历史数据&#xff0c;美国初请失业率人数从25…

LabVIEW检测润滑油中的水分和铁颗粒

LabVIEW检测润滑油中的水分和铁颗粒 润滑油广泛应用于现代机械设备&#xff0c;由于工作环境日益恶劣&#xff0c;润滑油经常被水分乳化&#xff0c;加速对机械设备的腐蚀。此外&#xff0c;润滑油还受到机械零件摩擦中产生的Fe颗粒的污染&#xff0c;削弱了其机械润滑效果。润…

操作视频的开始与暂停

调用 ref.current.play() 方法来播放视频&#xff1b; 如果视频需要暂停&#xff0c;我们调用 ref.current.pause() 方法来暂停视频。 通过 useRef 创建的 ref 操作视频的开始与暂停 当用户点击按钮时&#xff0c;根据当前视频的状态&#xff0c;我们会开始或暂停视频&…

图解 LeetCode 算法汇总——链表

本文首发公众号&#xff1a;小码A梦 一般数据主要存储的形式主要有两种&#xff0c;一种是数组&#xff0c;一种是链表。数组是用来存储固定大小的同类型元素&#xff0c;存储在内存中是一片连续的空间。而链表就不同于数组。链表中的元素不是存储在内存中可以是不连续的空间。…