数据结构之堆的基本操作

本文介绍了堆的数据结构,特别是完全二叉树的概念,并详细阐述了堆的基本操作,包括插入和删除。插入操作通过顺序表在堆尾部插入元素并维护堆属性;删除操作则涉及堆顶元素与末尾元素交换后删除,通过下沉过程保持堆的性质。堆排序通过反复删除堆顶元素,最终得到有序数组。文章附带了测试代码,欢迎读者交流讨论。
摘要由CSDN通过智能技术生成

在实现堆的操作之前,先来明白什么是堆?

  • 堆中某个节点的值总是不大于或不小于其父节点的值
  • 堆总是一棵完全二叉树。

要说到完全二叉树,直观的判断就是,层序遍历一棵树,如果树的某个节点没有右子树或者是没有子树,那么从这个节点后面的所有节点都不能够有任何子树。
这里写图片描述

接下来我们说说堆。堆分为大堆跟小堆。大堆的意思就是每一个节点都是它这个节点树的最大值。小堆同理是最小值。

基本操作与实现

基本操作

//heap.h

#pragma once

#include <stdio.h>
#include <stddef.h>

#define HEAPMAX 1000

typedef char HeapType;

typedef int(*Compare)(int a, int b);//函数指针,来确定将来的堆是最大堆还是最小堆

typedef struct Heap{
  HeapType data[HEAPMAX];
  size_t size;
  Compare cmp;
}Heap;

void HeapInit(Heap* heap, Compare cmp);//初始化堆

void HeapInsert(Heap* heap, HeapType to_insert);//在堆中插入

int HeapRoot(Heap* heap, HeapType* root);//取堆顶元素

void HeapErase(Heap* heap);//删除堆顶元素

void HeapDestroy(Heap* heap);//销毁堆

void HeapCreat(Heap* heap, HeapType array[], size_t size);//根据一个数组创建堆

void HeapSort(HeapType array[], size_t size);//堆排

实现

其实堆的操作都很简单,但是最核心的两个就是插入与删除。这两个我们单独来说。

#include "heap.h"

#define HEAD printf("===================%s====================\n",__FUNCTION__);

void HeapPrintChar(Heap* heap)
{
  if(heap == NULL) {
    return;
  }
  size_t i = 0;
  for(; i < heap->size; ++i) {
    printf("%c ", heap->data[i]);
  }
  printf("\n");
}


int Greater(int a, int b)
{
  return a > b ? 1 : 0;
}

int Less(int a, int b)
{
  return a < b ? 1 : 0;
}

void HeapInit(Heap* heap, Compare cmp)//初始化堆
{
  if(heap == NULL) {
    return;
  }
  heap->size = 0;
  heap->cmp = cmp;
  return;
}

int HeapRoot(Heap* heap, HeapType* root)//取堆顶元素
{
  if(heap == NULL) {
    return 0;
  }
  if(heap->size == 0) {
    return 0;
  }
  *root = heap->data[0];
  return 1;
}

void HeapDestroy(Heap* heap)//销毁堆
{
  if(heap == NULL) {
    return;
  }
  heap->size = 0;
  heap->cmp = NULL;
  return;
}

void HeapCreat(Heap* heap, HeapType array[], size_t size)//根据一个数组创建堆
{
  if(heap == NULL || array == NULL || size == 0) {
    return;
  }
  size_t i = 0;
  for(; i < size; ++i) {
    HeapInsert(heap, array[i]);
  }
  return;
}

插入

我们在实现堆的时,利用一个顺序表来实现。由于是层序遍历,且堆是完全二叉树。所以顺序表完全能够处理好堆的基本操作。再者,定义一个函数指针,这个函数指针可以确定我们的堆是大堆还是小堆。接着就是插入了,由于要考虑到插入完毕后还是堆,那么就要考虑到堆的性质,首先是完全二叉树,接着每个节点都是它节点子树的最大值或最小值。插入很简单,我们只需要在顺序表尾部插入即可。那如何保证堆的第二条性质呢?
这里写图片描述
所以在插入的时候,要对插入的元素与它的父节点进行比较。而如何确定它的父节点呢?这很简单,由于是层序遍历在顺序表内。那么一个节点的父节点的下标就是它自己的下标减一除二。即parent = (child - 1)/2

//实现

void Swap(HeapType* a, HeapType* b)
{
  HeapType tmp = *a;
  *a = *b;
  *b = tmp;
}

void HeapInsert(Heap* heap, HeapType to_insert)//在堆中插入
{
  if(heap == NULL) {
    return;
  }
  if(heap->size >= HEAPMAX) {
    return;
  }
  heap->data[heap->size] = to_insert;
  ++heap->size;
  size_t child = heap->size - 1;
  size_t parent = (child - 1) / 2;//父节点

  while(child >= 0 && child < heap->size &&
        parent >= 0 && parent < heap->size) {
    if(heap->data[child] > heap->data[parent]){//判断是否需要交换
      Swap(&heap->data[child], &heap->data[parent]);
      child = parent;//交换完毕后,要上浮,此时子节点变成之前的父节点,以此类推
      parent = (child - 1) / 2;
    } else {
      break;
    }
  }
  return;
}

删除

在实现完插入之后,接下来就是删除。同样,删除也得满足删除后的树仍旧是一个堆。且满足堆的基本条件。这里,我们的思路采用的是删除堆顶。具体如下:
这里写图片描述
这里,我们在删除之前,先将堆顶元素与最后一个元素交换,然后删除最后一个元素,也就是结构体内的size减一。删除完毕后,开始调整。调整的过程就是下沉。此时堆顶是6,先将节点元素与其左右子树分别比较。将左右子树中较小的与节点交换。如果没有右子树,那么直接与左子树交换。一次交换完毕后,此时新节点等于交换的子树。以此类推。这就是下沉。

//实现

void HeapErase(Heap* heap)//删除堆顶元素
{
  if(heap == NULL) {
    return;
  }
  if(heap->size == 0) {
    return;
  }
  Swap(&heap->data[heap->size - 1], &heap->data[0]);
  --heap->size;
  size_t parent = 0;
  size_t child = 2*parent + 1;//交换并删除

  while(child >= 0 && child< heap->size &&
        parent >= 0 && parent < heap->size) {
    if((child + 1) < heap->size){//判断是否有右子树,并且取出其中较小的
      if(!heap->cmp(heap->data[child], heap->data[child+1])) {
        child = child + 1;
      }
    }
    if(heap->data[child] > heap->data[parent]) {//判断是否需要下沉
      Swap(&heap->data[child], &heap->data[parent]);
      parent = child;
      child = 2*parent + 1;
    } else {
      break;
    }
  }
  printf("\n");
  return;
}

堆排

在实现完堆的基本操作以后,我们可以考虑一下堆排序的实现。我们其实可以发现,在删除堆内元素的时候每次删除的都是最大的一个。并且最大或最小的一个都沉底了。
这里写图片描述
我们发现,每次删除一次,后面的元素都是一个有序的。而删除到最后,整个顺序表就是一个有序的顺序表。如果这时候将size放到最后,那么此时恰好就是一个有序的数组。

//实现

void HeapSort(HeapType array[], size_t size)//堆排
{
  if(array == NULL || size == 0) {
    return;
  }
  Heap heap;
  HeapInit(&heap, Greater);
  HeapCreat(&heap, array, size);//先将数组插入堆中
  size_t i = 0;
  for(; i < size; ++i) {
    HeapErase(&heap);//删除堆内所有元素
  }
  heap.size = size;
  i = 0;
  for(; i < size; ++i) {
    array[i] = heap.data[i];//把堆内元素复制到数组中,完成排序
  }
  return;
}

测试代码如下

void TestInsert()
{
  HEAD;
  Heap heap;
  HeapInit(&heap, Greater);
  HeapInsert(&heap, 'b');
  HeapInsert(&heap, 'z');
  HeapInsert(&heap, 'a');
  HeapInsert(&heap, 'f');
  HeapInsert(&heap, 'd');
  HeapInsert(&heap, 'c');
  HeapInsert(&heap, 'n');
  HeapPrintChar(&heap);
}

void TestRoot()
{
  HEAD;
  Heap heap;
  HeapInit(&heap, Greater);
  HeapInsert(&heap, 'b');
  HeapInsert(&heap, 'z');
  HeapInsert(&heap, 'a');
  HeapInsert(&heap, 'f');
  HeapInsert(&heap, 'd');
  HeapInsert(&heap, 'c');
  HeapInsert(&heap, 'n');
  HeapType root;
  int ret = HeapRoot(&heap, &root);
  printf("ret expected 1, ret actual %d\n",ret);
  printf("root expected z, root actual %c\n",root);
}

void TestErase()
{
  HEAD;
  Heap heap;
  HeapInit(&heap, Greater);
  HeapInsert(&heap, 'b');
  HeapInsert(&heap, 'z');
  HeapInsert(&heap, 'a');
  HeapInsert(&heap, 'f');
  HeapInsert(&heap, 'd');
  HeapInsert(&heap, 'c');
  HeapInsert(&heap, 'n');
  HeapPrintChar(&heap);
  printf("\n");
  HeapErase(&heap);
  HeapPrintChar(&heap);
  HeapErase(&heap);
  HeapPrintChar(&heap);
  HeapErase(&heap);
  HeapPrintChar(&heap);
}

void TestSort()
{
  HEAD;
  HeapType array[] = {'c', 'd', 'a', 'f', 'z', 'e'};
  size_t size = sizeof(array)/sizeof(array[0]);
  HeapSort(array, size);
  size_t i = 0;
  for(; i < size; ++i) {
    printf("%c ", array[i]);
  }
  printf("\n");
}

void TestCreat()
{
  HEAD;
  Heap heap;
  HeapInit(&heap, Greater);
  HeapType array[] = {'c', 'd', 'a', 'f', 'z', 'e'};
  size_t size = sizeof(array)/sizeof(array[0]);
  HeapCreat(&heap, array, size);
  HeapPrintChar(&heap);
}

int main()
{
  TestInsert();
  TestRoot();
  TestErase();
  TestCreat();
  TestSort();



  printf("\n");
  printf("\n");
  printf("\n");
  printf("\n");
  printf("\n");


  return 0;
}

这里写图片描述


欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值