堆与堆排序算法实现分享

在计算机科学中,堆是一种特殊的完全二叉树数据结构,常被用于实现优先队列等应用场景。本文将通过一个C语言示例,详细介绍如何实现一个基于数组的大顶堆及其基本操作——包括堆的创建、插入元素(入堆)、删除最大元素(出堆)以及利用这些操作实现堆排序算法。本代码实例主要涉及到了一个Heap结构体的定义、堆的创建、元素的插入与删除,以及堆排序的整个过程。

创建项目,分别是heap.h,heap.c,test.c

堆的定义与结构体设计

堆是一种每个节点的值都大于或等于其子节点值的完全二叉树(大顶堆),或者每个节点的值都小于或等于其子节点值的完全二叉树(小顶堆)。在我们的示例中,我们实现了一个大顶堆。

如下代码所示:其中还包含了一些声明以及头文件的引用

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <math.h>

typedef int DataType;
typedef struct Heap
{
	DataType* data;
	int maxSize;
	int curSize;
}Heap;

Heap* createHeap(int maxSize);
void swap(Heap* heap, int pos1, int pos2);
void moveUp(Heap* heap, int curPos);
void insertHeap(Heap* heap, DataType data);
DataType popHeap(Heap* heap);
int moveDown(Heap* heap, int curPos);

这里,Heap结构体包含三个成员:指向堆数据的指针data、堆的最大容量maxSize以及当前堆的实际大小curSize

有如下函数,分别是堆的操作函数,创建堆,插入元素(入堆),删除最大元素(出堆),上渗和下渗。

我们先给出堆排序的基本步骤:

堆排序是一种利用堆的性质进行排序的方法。其基本思想是:

  1. 先将待排序序列构造成一个大顶堆。
  2. 将堆顶元素(即最大值)与堆尾元素交换,此时序列末尾就是最大值。
  3. 将剩余的n-1个元素重新调整为大顶堆。
  4. 重复步骤2和3,直到整个序列有序。

在我们的代码中,通过不断调用popHeap函数并输出,实现了从大到小的堆排序。注意,本文代码中的操作与堆排序的操作略有不同,主要在第二个步骤的实现上。

代码示例:

#include"heap.h"

Heap* createHeap(int maxSize)
{
	//申请堆内存
	Heap* heap = (Heap*)malloc(sizeof(Heap));
	assert(heap);
	//申请数组内存
	heap->data = (DataType*)malloc((maxSize+1) * sizeof(DataType));
	assert(heap->data);
	//初始化
	heap->maxSize = maxSize;
	heap->curSize = 0;
}

//交换数组中(二叉树)的两个数据
void swap(Heap* heap, int pos1, int pos2)
{
	DataType temp = heap->data[pos1];
	heap->data[pos1] = heap->data[pos2];
	heap->data[pos2] = temp;
}

//向上移动:传入元素的下标和堆
void moveUp(Heap* heap, int curPos)
{
	//最极端的结束条件是移动到最上面(也就是下标为1的位置)
	while (curPos!=1)
	{
		//得到父节点下标
		int parentPos = curPos / 2;
		//如果父节点小于该节点
		if (heap->data[parentPos] < heap->data[curPos])
		{
			//交换俩个节点数据
			swap(heap, curPos, parentPos);
			//更新下标
			curPos = parentPos;
		}
		else
		{
			//如果插入元素小于父节点值,那么说明不用调整
			return;
		}
	}
}

//入堆(生成堆)
void insertHeap(Heap* heap,DataType data)
{
	//如果堆满
	if (heap->curSize == heap->maxSize)
	{
		printf("堆满\n");
		return;
	}
	//注意这里是先++,而后再存入数据(因为下标为0的位置不存放数据)
	//目的是保证下标和完全二叉树的标号保持一致
	heap->data[++(heap->curSize)] = data;
	//插入之后向上渗透,调整位置
	moveUp(heap, heap->curSize);
}

int moveDown(Heap* heap, int curPos)
{
	//这里每次删除的是该堆中最大的(以大顶堆为例)
	int Pos = curPos;
	//左孩子和右孩子的下标
	int Lchild = curPos * 2;
	int Rchild = Lchild + 1;
	//计算完全二叉树的深度
	int depth = log2(heap->curSize) + 1;
	//最极端的结束条件是移动到最下面(就是最下面的一层)并且不能让下标越界
	while (log2(curPos) + 1 < depth && Rchild <= heap->curSize)
	{
		//选择两条路(两个孩子)中较大者,进行比较下渗
		if (heap->data[Rchild] > heap->data[Lchild])
		{
			//由于是最大值下渗,直接交换就行,不用再考虑比较
			swap(heap, curPos, Rchild);
			//更新当前节点下标
			curPos = Rchild;
		}
		else
		{
			//由于是最大值下渗,直接交换就行,不用再考虑比较
			swap(heap, curPos, Lchild);
			//更新当前节点下标
			curPos = Lchild;
		}
		//更新孩子节点
		Lchild = curPos * 2;
		Rchild = Lchild + 1;
	}
	return curPos;
}

//出堆(向下渗透)
DataType popHeap(Heap*heap)
{
	if (heap->curSize == 0)
	{
		return;
	}
	//保存最大值,后续会返回
	DataType temp = heap->data[1];
	//完成下渗操作
	int curPos=moveDown(heap, 1);
	//移动到底部之后,调整该节点位置至最后
	//直接用原来最后一个节点的数据覆盖掉当前节点数据(是否破坏了原来的顺序呢?存疑)
	//如果已经移动到最后一个,那么直接删除即可,再上渗就回去了
	if (curPos != heap->curSize)
	{
		heap->data[curPos] = heap->data[heap->curSize];
		//然后对这个节点上滤
		moveUp(heap, curPos);
	}
	//数组伪删除
	heap->curSize--;
	return temp;
}

样例测试:

#include "heap.h"

int main()
{
	Heap* heap = createHeap(7);
	insertHeap(heap, 50);
	insertHeap(heap, 16);
	insertHeap(heap, 41);
	insertHeap(heap, 56);
	insertHeap(heap, 1);
	insertHeap(heap, 23);
	insertHeap(heap, 10);

	printf("堆遍历\n");
	for (int i = 1; i <= heap->curSize; i++)
	{
		printf("%d ", heap->data[i]);
	}
	printf("\n");

	printf("堆排序:\n");
	while (heap->curSize != 0)
	{
		printf("%d ", popHeap(heap));
	}
	return 0;
}

测试结果:

  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值