【数据结构入门】-堆的实现以及堆排序(1)

简介: 【数据结构入门】-堆的实现以及堆排序(1)

堆的实现

接口函数

//初始化
void HeapInit(HP* php);
//插入数据
void HeapPush(HP* php,HPDataType x);
//删除数据
void HeapPop(HP* php);
//取堆顶数据
HPDataType HeapTop(HP* php);
//是否为空
bool HeapEmpty(HP* php);
//堆中元素的个数
int HeapSize(HP* php);


结构

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


初始化

//初始化
void HeapInit(HP* php)
{
  assert(php);
  php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
  if (php->a = NULL)
  {
  perror("malloc fail\n");
  return;
  }
  php->size = 0;
  php->capacity = 4;
}


数据的插入

//插入数据
void HeapPush(HP* php, HPDataType x)
{
  assert(php);
  //既然是插入数据,那么首先要检查是否需要扩容
  if (php->size == php->capacity)
  {
  HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
  if (tmp == NULL)
  {
    perror("realloc fail");
    return;
  }
  php->a = tmp;
  php->capacity *= 2;
  }
  php->a[php->size] = x;//size是最后一个数据的下一个数据的下标
  php->size++;
  AdjustUp(php->a, php->size - 1);
}

1.png

这里需要注意的是size是最后一个位置的下一个位置的下标。

当我们插入完数据后并没有完成我们的任务,我们还应该保证插入数据后此时依然符合堆的规则。

而这个规则就是向上调整,从插入数据的位置开始向上调整。


交换数据

//交换数据
void swap(HPDataType* p1, HPDataType* p2)
{
  HPDataType tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}



向上调整算法

//向上调整
void AdjustUp(HPDataType* a, int child)
{
  //父亲就是(孩子-1)/2
  int parent = (child - 1) / 2;
  while (child>0)
  {
  if (a[child] > a[parent])
  {
    swap(&a[child], &a[parent]);
    child = parent;
    parent = (child - 1) / 2;
  }
  else
  {
    break;
  }
  }
}


向上调整时需要注意我们的while循环条件,应该是child>0,而不是parent>=0,这是因为parent是永远>=0的,但是由于break的作用导致程序并不会死循环,最终会由break结束整个循环。


2.png

其实到这里我们的插入数据来进行堆的插入就已经算完成了。


删除数据

既然要删除数据的话,我们就应该知道我们应该删除哪里的数据,如果我们要删除尾的话,当然很好删除,但是如果仔细想一想的话删除尾的话好像没有什么意义;所以说这里我们要进行删除的是堆顶的数据,不仅仅是大根堆,小根堆也是会删除堆顶的数据。

那我们怎么删除堆顶的数据呢?首先挪动删除数据这个方法是不可以的,因为挪动数据的话效率会显得非常的低,而且父子兄弟的关系全都乱了。

3.png


//删除数据
void HeapPop(HP* php)
{
  assert(php);
  assert(!HeapEmpty(php));//没有数据时就不要删除了
  swap(&php->a[0], &php->a[php->size - 1]);
  php->size--;
  AdjustDown(php->a, php->size, 0);
}


向下调整算法

//向下调整

void AdjustDown(HPDataType* a, int n, int parent)
{
  int child = parent * 2 + 1;
  while (child < n)
  {
  if (child + 1 < n && a[child] < a[child + 1])
  {
    child++;
  }
  if (a[child] > a[parent])
  {
    swap(&a[child], &a[parent]);
    parent = child;
    child = parent * 2 + 1;
  }
  else
  {
    break;
  }
  }
}


这里有一个问题,因为a[child] < a[child + 1]这个地方我们怕越界,所以我们在前面加上了child + 1 < n来确保右孩子存在。如果我们开辟空间的时候开成偶数倍的空间是不是就不用担心越界的问题了呢?答案是否定的,如果是这样的话就成了只关注越界问题而不关注数据是否为有效数据。如果说右孩子这里的值是一个随机值(当然这并不是我们堆中的有效数据),恰好这个随机值比我们的左孩子要大,此时由于child++,所以我们比较的就是右孩子和父亲之间的关系了,但问题是右孩子这里是一个随机值啊,并不是堆中的有效数据,然后程序运行下来就会造成一系列的问题。所以开辟成偶数倍的空间并不会解决问题。


堆是否为空

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


取堆顶数据

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


堆中有多少元素

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


测试


4.png

如果我们要取前k个数据,请看:

5.png


堆实现总代码

test.c


#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
int main()
{
  HP hp;
  HeapInit(&hp);
  HeapPush(&hp, 4);
  HeapPush(&hp, 18);
  HeapPush(&hp, 42);
  HeapPush(&hp, 12);
  HeapPush(&hp, 2);
  HeapPush(&hp, 3);
  int i = 0;
  scanf("%d", &i);
  while (!HeapEmpty(&hp) && i--)
  {
  printf("%d ", HeapTop(&hp));
  HeapPop(&hp);
  }
  printf("\n");
  return 0;
}


Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//初始化
void HeapInit(HP* php)
{
  assert(php);
  php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
  if (php->a == NULL)
  {
  perror("malloc fail\n");
  return;
  }
  php->size = 0;
  php->capacity = 4;
}
void swap(HPDataType* p1, HPDataType* p2)
{
  HPDataType tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}
//向上调整
void AdjustUp(HPDataType* a, int child)
{
  //父亲就是(孩子-1)/2
  int parent = (child - 1) / 2;
  while (child>0)
  {
  if (a[child] > a[parent])
  {
    swap(&a[child], &a[parent]);
    child = parent;
    parent = (child - 1) / 2;
  }
  else
  {
    break;
  }  
  }
}
//插入数据
void HeapPush(HP* php, HPDataType x)
{
  assert(php);
  //既然是插入数据,那么首先要检查是否需要扩容
  if (php->size == php->capacity)
  {
  HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
  if (tmp == NULL)
  {
    perror("realloc fail");
    return;
  }
  php->a = tmp;
  php->capacity *= 2;
  }
  php->a[php->size] = x;//size是最后一个数据的下一个数据的下标
  php->size++;
  AdjustUp(php->a, php->size - 1);
}
//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
  int child = parent * 2 + 1;
  while (child < n)
  {
  if (child + 1 < n && 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* php)
{
  assert(php);
  assert(!HeapEmpty(php));//没有数据时就不要删除了
  swap(&php->a[0], &php->a[php->size - 1]);
  php->size--;
  AdjustDown(php->a, php->size, 0);
}
HPDataType HeapTop(HP* php)
{
  assert(php);
  return php->a[0];
}
bool HeapEmpty(HP* php)
{
  assert(php);
  return php->size == 0;
}
int HeapSize(HP* php)
{
  assert(php);
  return php->size;
}
void HeapDestroy(HP* php)
{
  assert(php);
  free(php->a);
  php->a = NULL;
  php->capacity = php->size = 0;
}


Heap.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
  HPDataType* a;
  int size;
  int capacity;
}HP;
//初始化
void HeapInit(HP* php);
//插入数据
void HeapPush(HP* php,HPDataType x);
//删除数据
void HeapPop(HP* php);
//取堆顶数据
HPDataType HeapTop(HP* php);
//是否为空
bool HeapEmpty(HP* php);
//堆中元素的个数
int HeapSize(HP* php);
void HeapDestroy(HP* php);
void HeapInitArray(HP* php, int* a, int n);
void AdjustDown2(int* a, int n, int parent);


目录
相关文章
|
1月前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
38 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
68 16
|
2月前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
84 1
|
2月前
|
算法 搜索推荐
数据结构与算法学习十八:堆排序
这篇文章介绍了堆排序是一种通过构建堆数据结构来实现的高效排序算法,具有平均和最坏时间复杂度为O(nlogn)的特点。
75 0
数据结构与算法学习十八:堆排序
|
2月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
2月前
|
存储 机器学习/深度学习 算法
探索数据结构:入门及复杂度的解锁
探索数据结构:入门及复杂度的解锁
|
2月前
|
存储 算法 Java
【用Java学习数据结构系列】用堆实现优先级队列
【用Java学习数据结构系列】用堆实现优先级队列
36 0
|
2月前
|
存储 算法
【数据结构】二叉树——顺序结构——堆及其实现
【数据结构】二叉树——顺序结构——堆及其实现
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
140 9
|
26天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
24 1