算法给小码农堆魂器--铁血柔情

简介: 数据结构中的堆不同于操作系统中的堆(操作系统中的堆是用来存储动态内存的),数据结构中的堆是数据的存储方式。数据结构中的堆是完全二叉树

文章目录


数据结构中的堆不同于操作系统中的堆(操作系统中的堆是用来存储动态内存的),数据结构中的堆是数据的存储方式。数据结构中的堆是完全二叉树

既然堆是完全二叉树的形式存储的那么就非常适合用数组的方式来表示

堆的概念及结构

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

大堆:树中一个树及子树中,任何一个父亲都大于等于孩子

小堆:树中一个树及子树中,任何一个父亲都小于等于孩子

堆的性质

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

堆总是一棵完全二叉树**

堆的结构(这里实现大堆)

既然还是数组的结构的话就还是顺序表的处理方式,数组指针,size,capacity。虽然物理上我们是用顺序表的方式来表示,但是他实际上表示的数据是完全二叉树。

堆的结构体

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

堆初始化函数HeapInit

就是一个指向NULL的数组,size 和 capacity都为零

//堆初始化函数
void HeapInit(HP* hp)
{
  assert(hp);
  hp->a = NULL;
  hp->size = hp->capacity = 0;
}

堆销毁函数HeapDestroy

由于数组是动态开辟的,所以用完后需要销毁的,不然会内存泄漏

//堆销毁函数
void HeapDestroy(HP* hp)
{
  assert(hp);
  free(hp->a);
  hp->size = hp->capacity = 0;
}

堆打印函数HeapPrint

可以想象成一种快速调试,类似于单片机中的串口打印看数据收发情况

//堆打印函数
void HeapPrint(HP* hp)
{
  int i = 0;
  for (i = 0; i < hp->size; i++)
  {
    printf("%d ", hp->a[i]);
  }
  printf("\n");
}

向上调整函数AdjustUp

为了不影响数据的存储形式(大堆还得是大堆),插入数据就不能破坏大堆的形式,我们需要把堆插入函数中的数据调整给剥离出来

我们可以看到插入的这个数据对其他的节点并没有什么影响,有影响的只是这个节点到根这条路径上的节点,如何解决对这条路径的影响呢,我们可以形象的看到仅仅是在这条路径上进行向上调整

通过parent = (child-1)/2 找到父亲节点,与之进行比较,然后父亲小就交换位置(大堆),然后交换后就在找上面的父亲节点,直到找到父亲大于孩子,就不交换了

//向上调整函数
void AdjustUp(HPDataType* a, int child)
{
  assert(a);
  int parent = (child - 1) / 2;
  while (child>0)
  {
    if (a[parent] < a[child])//父亲小于孩子就交换(大堆)
    {
      a[parent] = a[parent] ^ a[child];
      a[child] = a[parent] ^ a[child];
      a[parent] = a[parent] ^ a[child];
      //交换好后重新称呼一下孩子与父亲
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}

堆插入函数HeapPush

//堆插入函数(要保持原来形式,大堆还是大堆,小堆就还是小堆)
void HeapPush(HP* hp, HPDataType x)
{
  assert(hp);
  //判断扩容
  if (hp->size == hp->capacity)
  {
    //容量给新的容量
    int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
    //扩容
    HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
    //增容失败
    if (!tmp)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    //增容成功
    hp->a = tmp;
    hp->capacity = newcapacity;   
  }
  //放数据
  hp->a[hp->size] = x;
  hp->size++;
  //实现大堆
  //这个部分的向上调整其他地方也用的到就把他剥离出来
  AdjustUp(hp->a, hp->size - 1);//child下标
}

判断堆是否为空函数HeapErmpy

//判断堆是否为空函数
bool HeapErmpy(HP* hp)
{
  assert(hp);
  return hp->size == 0;
}

返回堆大小函数HeapSize

//返回堆大小函数
int HeapSize(HP* hp)
{
  assert(hp);
  return hp->size;
}

下面还会用到交换函数,上面也有那么我们不妨把他剥离出来封装一下,就不需要重复写了

交换函数Swap

//交换函数
void Swap(HPDataType* px, HPDataType* py)
{
  *px = *px ^ *py;
  *py = *px ^ *py;
  *px = *px ^ *py;
}

向下调整函数AdjustDown

//向下调整函数
void AdjustDown(HPDataType* a, int size, int parent)
{
  assert(a);
  //创建一个孩子变量,有两个孩子就在这个上加1就行
  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;
    }
  }
}

堆删除函数HeapPop

我们可以认为假想根和堆的最后一个元素交换后,把最后一个删除,然后再对堆进行操作,你会发现,我们没有破坏原来的整体结构

//堆删除函数(删除的是堆顶数据也就是取最值)
//还有不可能一直删的所以我们需要一个判空函数
void HeapPop(HP* hp)
{
  assert(hp);
  assert(!HeapErmpy(hp));
  //根和堆最后一个元素交换
  Swap(&hp->a[0],&hp->a[hp->size-1]);
  //把最后一个删除,就是我们想要删除的元素
  hp->size--;
  //向下调整
  AdjustDown(hp->a,hp->size,0);
}


代码

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;
//堆初始化函数
extern void HeapInit(HP* hp);
//堆销毁函数
extern void HeapDestroy(HP* hp);
//向上调整函数
extern void AdjustUp(HPDataType* a,int child);
//堆插入函数
extern void HeapPush(HP* hp,HPDataType x);
//堆打印函数
extern void HeapPrint(HP* hp);
//向下调整函数
extern void AdjustDown(HPDataType* a, int size, int parent);
//堆删除函数
extern void HeapPop(HP* hp);
//判断堆是否为空函数
extern bool HeapErmpy(HP* hp);
//交换函数
extern void Swap(HPDataType* px, HPDataType* py);
//返回堆大小函数
extern int HeapSize(HP* hp);

Heap.c

#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;
//堆初始化函数
extern void HeapInit(HP* hp);
//堆销毁函数
extern void HeapDestroy(HP* hp);
//向上调整函数
extern void AdjustUp(HPDataType* a,int child);
//堆插入函数
extern void HeapPush(HP* hp,HPDataType x);
//堆打印函数
extern void HeapPrint(HP* hp);
//向下调整函数
extern void AdjustDown(HPDataType* a, int size, int parent);
//堆删除函数
extern void HeapPop(HP* hp);
//判断堆是否为空函数
extern bool HeapErmpy(HP* hp);
//交换函数
extern void Swap(HPDataType* px, HPDataType* py);
//返回堆大小函数
extern int HeapSize(HP* hp);

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//堆初始化函数
void HeapInit(HP* hp)
{
  assert(hp);
  hp->a = NULL;
  hp->size = hp->capacity = 0;
}
//堆销毁函数
void HeapDestroy(HP* hp)
{
  assert(hp);
  free(hp->a);
  hp->size = hp->capacity = 0;
}
//向上调整函数
void AdjustUp(HPDataType* a, int size, int child)
{
  assert(a);
  int parent = (child - 1) / 2;
  while (child>0)
  {
    if (a[parent] < a[child])//父亲小于孩子就交换(大堆)
    {
      Swap(&a[parent], &a[child]);
      //交换好后重新称呼一下孩子与父亲
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}
//堆插入函数(要保持原来形式,大堆还是大堆,小堆就还是小堆)
void HeapPush(HP* hp, HPDataType x)
{
  assert(hp);
  //判断扩容
  if (hp->size == hp->capacity)
  {
    //容量给新的容量
    int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
    //扩容
    HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
    //增容失败
    if (!tmp)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    //增容成功
    hp->a = tmp;
    hp->capacity = newcapacity;   
  }
  //放数据
  hp->a[hp->size] = x;
  hp->size++;
  //实现大堆
  //这个部分的向上调整其他地方也用的到就把他剥离出来
  //向上调整一下堆的数据形式,使他还是大堆的形式
  AdjustUp(hp->a, hp->size, hp->size - 1);
}
//堆打印函数
void HeapPrint(HP* hp)
{
  assert(hp);
  int i = 0;
  for (i = 0; i < hp->size; i++)
  {
    printf("%d ", hp->a[i]);
  }
  printf("\n");
}
//判断堆是否为空函数
bool HeapErmpy(HP* hp)
{
  assert(hp);
  return hp->size == 0;
}
//返回堆大小函数
int HeapSize(HP* hp)
{
  assert(hp);
  return hp->size;
}
//交换函数
void Swap(HPDataType* px, HPDataType* py)
{
  *px = *px ^ *py;
  *py = *px ^ *py;
  *px = *px ^ *py;
}
//向下调整函数
void AdjustDown(HPDataType* a, int size, int parent)
{
  assert(a);
  //创建一个孩子变量,有两个孩子就在这个上加1就行
  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 HeapPop(HP* hp)
{
  assert(hp);
  assert(!HeapErmpy(hp));
  //根和堆最后一个元素交换
  Swap(&hp->a[0],&hp->a[hp->size-1]);
  //把最后一个删除,就是我们想要删除的元素
  hp->size--;
  //向下调整
  AdjustDown(hp->a,hp->size,0);
}


目录
相关文章
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
65 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
6月前
|
存储 机器学习/深度学习 算法
数据结构与算法:堆
朋友们大家好啊,本篇文章来到堆的内容,堆是一种完全二叉树,再介绍堆之前,我们首先对树进行讲解
数据结构与算法:堆
|
3月前
|
数据采集 算法
基于PSO粒子群算法的三角形采集堆轨道优化matlab仿真
该程序利用PSO算法优化5个4*20矩阵中的模块采集轨迹,确保采集的物品数量及元素含量符合要求。在MATLAB2022a上运行,通过迭代寻优,选择最佳模块组合并优化轨道,使采集效率、路径长度及时间等综合指标最优。具体算法实现了粒子状态更新、需求量差值评估及轨迹优化等功能,最终输出最优轨迹及其相关性能指标。
|
4月前
|
算法 Java 开发者
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
39 0
|
4月前
|
存储 算法 Java
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
65 0
|
4月前
|
算法 安全 调度
逆天改命!Python高级数据结构堆(Heap)与优先队列,让你的算法效率飙升至宇宙级!
【7月更文挑战第8天】Python的heapq模块和queue.PriorityQueue实现了堆和优先队列,提供高效算法解决方案。堆用于Dijkstra算法求解最短路径,例如在图论问题中;PriorityQueue则在多线程下载管理中确保高优先级任务优先执行。这两个数据结构提升效率,简化代码,是编程中的强大工具。
50 0
|
5月前
|
存储 算法
【数据结构和算法】---二叉树(2)--堆的实现和应用
【数据结构和算法】---二叉树(2)--堆的实现和应用
23 0
|
6月前
|
存储 机器学习/深度学习 算法
数据结构与算法⑬(第四章_中_续二)堆解决Topk问题+堆的概念选择题
数据结构与算法⑬(第四章_中_续二)堆解决Topk问题+堆的概念选择题
53 3
|
5月前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
|
6月前
|
Arthas 监控 算法
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收算法评价标准、标记清除算法、复制算法、标记整理算法、分代垃圾回收算法等内容。
79 0
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法