【数据结构入门】-堆的实现以及堆排序(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);


目录
相关文章
|
10天前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
37 1
|
11天前
|
算法 搜索推荐
数据结构与算法学习十八:堆排序
这篇文章介绍了堆排序是一种通过构建堆数据结构来实现的高效排序算法,具有平均和最坏时间复杂度为O(nlogn)的特点。
50 0
数据结构与算法学习十八:堆排序
|
11天前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
11天前
|
存储 机器学习/深度学习 算法
探索数据结构:入门及复杂度的解锁
探索数据结构:入门及复杂度的解锁
|
13天前
|
存储 算法 Java
【用Java学习数据结构系列】用堆实现优先级队列
【用Java学习数据结构系列】用堆实现优先级队列
26 0
|
15天前
|
存储 算法
【数据结构】二叉树——顺序结构——堆及其实现
【数据结构】二叉树——顺序结构——堆及其实现
|
17天前
【数据结构】大根堆和小根堆
【数据结构】大根堆和小根堆
17 0
|
17天前
|
存储 缓存 应用服务中间件
Nginx入门 -- 基本数据结构中之ngx_hash_t
Nginx入门 -- 基本数据结构中之ngx_hash_t
32 0
|
11天前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
16 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
11天前
初步认识栈和队列
初步认识栈和队列
35 10