【数据结构】什么是堆,如何使用无序数组生成一个堆?

简介: 一、堆的概念及其介绍堆(Heap)是计算机科学中一类特殊的数据结构的统称,堆通常是一个可以被看做一棵完全二叉树的数组对象。如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

一、堆的概念及其介绍

堆(Heap)是计算机科学中一类特殊的数据结构的统称,堆通常是一个可以被看做一棵完全二叉树的数组对象。如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树顺序存储方式存储 在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆满足下列性质:

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

  • 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
  • 每个结点的值都小于或等于具左石孩子结点 的值,称为小顶堆。
  • 堆总是一棵完全二叉树。

结构图示

c7c5913e3f1e4e8ca27cfcee1716e33b.png

二、如何使用无序序列构建一个堆?

如果有一组无序的数组,{50,10,90,30,70,40,80,60,20},我们将它抽象为逻辑结构,z这时怎么将无序序列变成一个大堆或者小堆呢?


3ce1cf483f3c40359a20a869465701d2.png

向上调整法

从下标为1的位置开始,也就是图中10的位置,依次进行向上调整,每次将更小的换到上面,

题目中有个小问题,如何找到父节点?

5a1037c4be874c3393e5f52226d7a677.png

  • 父节点 = (子结点-1)/2
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
typedef int HeapDataType;
void swap(HeapDataType* a, HeapDataType* b)   //交换
{
  HeapDataType temp;
  temp = *a;
  *a = *b;
  *b = temp;
}
void Adjustup(HeapDataType* arr, int child)   //向上调整函数
{
  int parent = (child - 1) / 2;
  while (child > 0)
  {
    if (arr[child] < arr[parent])
    {
      swap(&arr[child], &arr[parent]);
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}
void CreateHeap(int* a, int n)      //使用无序数组创建堆
{
  for (int i = 1; i < n; i++)
  {
    Adjustup(a, i);
  }
  for (int i = 0; i < n; i++)
  {
    printf("%d ", a[i]);
  }
}
int main()
{
  int arr[] = { 50,10,90,30,70,40,80,60,20 };
  CreateHeap(arr, sizeof(arr) / sizeof(int));
}

向下调整法(更优)

从非叶节点的最后一个数据的下标开始,每次取出孩子中较大或较小的数(看是大堆还是小堆)向下进行调整,由于每多一层,下层是上层的二倍,这种办法直接可以省略掉最后一层,也可以达到建堆的目的,所以这种办法为更优的办法。

由于需要向下调整,所以这种办法需要找到子节点,我们已经知道父结点的运算了,子结点就是父节点的逆运算。

2f8affda3d79406db0e52d2d42387b45.png

  • 子节点:父节点*2+1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
typedef int HeapDataType;
void swap(HeapDataType* a, HeapDataType* b)   //交换
{
  HeapDataType temp;
  temp = *a;
  *a = *b;
  *b = temp;
}
void AdjustDown(HeapDataType* arr, int n, int parent) //向下调整
{
  assert(arr);
  int child = parent * 2 + 1;
  while (child < n)
  {
    if (child<n - 1 && arr[child] > arr[child + 1])
    {
      child = child + 1;
    }
    if (arr[child] < arr[parent])
    {
      swap(&arr[child], &arr[parent]);
    }
    parent = child;
    child = child * 2 + 1;
  }
}
void CreateHeap(int* a, int n)          //使用无序数组创建堆
{
  for (int i = (n - 2) / 2; i >= 0; i--)
  {
    AdjustDown(a, n, i);
  }
  for (int i = 0; i < n; i++)
  {
    printf("%d ", a[i]);
  }
}
int main()
{
  int arr[] = { 50,10,90,30,70,40,80,60,20 };
  CreateHeap(arr, sizeof(arr) / sizeof(int));
}

三、C语言实现堆的基本操作

结构体创建与销毁

顺序存储方式实现堆采用顺序表进行存储。

typedef int HeapDataType;
typedef struct Heap
{
  HeapDataType* arr;      
  int size;     //当前大小
  int capacity;   //当前容量上限
}Heap;
void HeapDestroy(Heap* ph)
{
  assert(ph);     
  free(ph->arr);
  ph->arr = NULL;
  ph->capacity = 0;
  ph->size = 0;
  free(ph);     //由于顺序表空间是申请堆空间的内存所以需要进行释放
}

获取堆顶数据与个数及堆的判空

HeapDataType HeapTop(Heap* ph) 
{
  assert(ph);
  return ph->arr[0];
}
int HeapSize(Heap* ph)
{
  assert(ph);
  return ph->size;
}
int HeapEmpty(Heap* ph)
{
  assert(ph);
  return ph->size == 0;
}

堆的插入与删除

插入时需要注意空间不足问题,如果空间不足,需要进行二次开辟空间,插入时直接插入到堆尾,然后利用上面写好的向上调整函数。

删除时删掉堆顶数据,将堆底数据拿到堆顶,在进行向下调整,即可保证堆性质不变,依然保持原有的大/小堆。

void HeapPush(Heap* ph, HeapDataType x)
{
  assert(ph);
  if (ph->size == ph->capacity)
  {
    int newcapacity = ph->capacity == 0 ? 5 : ph->capacity * 2;
    HeapDataType* temp = (HeapDataType*)realloc(ph->arr, sizeof(int) * newcapacity);
    if (temp == NULL)
    {
      perror("realloc: error");
      return;
    }
    ph->arr = temp;
    ph->capacity = newcapacity;
  }
  ph->arr[ph->size] = x;
  Adjustup(ph->arr, ph->size - 1);
  ph->size++;
}
void HeapPop(Heap* ph)
{
  assert(ph);
  assert(ph->arr);
  assert(!HeapEmpty(ph));
  swap(&ph->arr[ph->size - 1], &ph->arr[0]);
  ph->size--;
  AdjustDown(ph->arr, ph->size, 0);
}

源代码分享

//heap.h
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
typedef int HeapDataType;
typedef struct Heap
{
  int* arr;
  int size;
  int capacity;
}Heap;
void HeapCreate(Heap* ph);
void HeapDestroy(Heap* ph);
void swap(HeapDataType* a, HeapDataType* b);
void Adjustup(HeapDataType* arr, int child);
void AdjustDown(HeapDataType* arr, int n, int parent);
void HeapPush(Heap* ph, HeapDataType x);
void HeapPop(Heap* ph);
HeapDataType HeapTop(Heap* ph);
int HeapSize(Heap* ph);
int HeapEmpty(Heap* ph);
//Heap.c
#include "Heap.h"
void HeapCreate(Heap* ph)
{
  assert(ph);
  ph->arr = NULL;
  ph->capacity = 0;
  ph->size = 0;
}
void HeapDestroy(Heap* ph)
{
  assert(ph);
  free(ph->arr);
  ph->arr = NULL;
  ph->capacity = 0;
  ph->size = 0;
  free(ph);
}
void swap(HeapDataType* a, HeapDataType* b)
{
  HeapDataType temp;
  temp = *a;
  *a = *b;
  *b = temp;
}
void Adjustup(HeapDataType* arr, int child)
{
  int parent = (child - 1) / 2;
  while (child > 0)
  {
    if (arr[child] < arr[parent])
    {
      swap(&arr[child], &arr[parent]);
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}
void AdjustDown(HeapDataType* arr, int n, int parent)
{
  assert(arr);
  int child = parent * 2 + 1;
  while(child<n)
  {
    if (child<n-1&&arr[child] > arr[child + 1])
    {
      child = child + 1;
    }
    if (arr[child] < arr[parent])
    {
      swap(&arr[child], &arr[parent]);
    }
    parent = child;
    child = child * 2 + 1;
  }
}
void HeapPush(Heap* ph, HeapDataType x)
{
  assert(ph);
  if (ph->size == ph->capacity)
  {
    int newcapacity = ph->capacity == 0 ? 5 : ph->capacity * 2;
    HeapDataType* temp = (HeapDataType*)realloc(ph->arr, sizeof(int) * newcapacity);
    if (temp == NULL)
    {
      perror("realloc: error");
      return;
    }
    ph->arr = temp;
    ph->capacity = newcapacity;
  }
  ph->arr[ph->size] = x;
  Adjustup(ph->arr, ph->size - 1);
  ph->size++;
}
void HeapPop(Heap* ph)
{
  assert(ph);
  assert(ph->arr);
  assert(!HeapEmpty(ph));
  swap(&ph->arr[ph->size - 1], &ph->arr[0]);
  ph->size--;
  AdjustDown(ph->arr, ph->size, 0);
}
HeapDataType HeapTop(Heap* ph) 
{
  assert(ph);
  return ph->arr[0];
}
int HeapSize(Heap* ph)
{
  assert(ph);
  return ph->size;
}
int HeapEmpty(Heap* ph)
{
  assert(ph);
  return ph->size == 0;
}
//test.c
void CreateHeap(int* a, int n)      //使用向上调整
{
  for (int i = 1; i < n; i++)
  {
    Adjustup(a, i);
  }
  for (int i = 0; i < n; i++)
  {
    printf("%d ", a[i]);
  }
}
void CreateHeap(int* a, int n)    //使用向下调整
{
  for (int i = (n - 2) / 2; i >= 0; i--)
  {
    AdjustDown(a, n, i);
  }
  for (int i = 0; i < n; i++)
  {
    printf("%d ", a[i]);
  }
}
int main()
{
  int arr[] = { 50,10,90,30,70,40,80,60,20 };
  CreateHeap(arr, sizeof(arr) / sizeof(int));
}

d7d3c9764adc43d09c554697b8c3b851.gif

✨本文收录于数据结构理解与实现

当你喜欢一篇文章时,点赞、收藏和关注是最好的支持方式。如果你喜欢我的文章,请不要吝啬你的支持,点赞👍、收藏⭐和关注都是对我最好的鼓励。感谢你们的支持!















目录
打赏
0
0
0
0
1
分享
相关文章
|
5月前
|
数据结构之 - 深入了解数组数据结构
数据结构之 - 深入了解数组数据结构
81 6
|
4月前
|
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
81 1
散列表的数据结构以及对象在JVM堆中的存储过程
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
80 23
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
169 64
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
117 5
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
192 16
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
101 4
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
97 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
6月前
|
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
101 5
【数据结构】优先级队列(堆)从实现到应用详解
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
221 1