二叉树的顺序实现之堆

1.什么是堆?

堆可以看作是一颗特殊的完全二叉树,每个节点的左右孩子的值都大于或小于该节点的值。
根据大小关系可以将堆分为大根堆(最大堆)和小根堆(最小堆)两大类。

大根堆:
在这里插入图片描述
小根堆:
在这里插入图片描述

2.堆的实现

堆的实现采用数组,堆的操作基于向下调整算法和向上调整算法

1)堆的创建

方法1:从倒数第1个非叶子节点直到根节点,开始执行向下调整算法
方法2:执行堆的插入操作

2)堆的插入

时间复杂度:O(logN)
将要插入的数据添加到数据的尾部,开始执行向上调整算法。

3)堆的删除

时间复杂度:O(logN)
堆只能删除堆顶元素
将堆顶元素和尾部元素进行交换,删除尾部元素,从根节点开始执行向下调整算法

4)堆排序

时间复杂度:O(NlogN)
一个大根堆进行堆排序可以得到一个递增的序列,
一个小根堆进行堆排序可以得到一个递减的序列
堆排序的过程:
将堆顶元素和尾部元素进行交换,从根节点开始执行向下调整算法,每次让尾部元素往前移动一位,直到只剩下一个堆顶元素时就不再调整

5)TopK:打印最小的或者最大的k个元素

拿到堆顶元素,进行堆的删除操作,重复这一步k次

3.源码

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define ORIGINSIZE 10	//最初的堆空间存储元素的个数

//大堆

typedef int DataType;	//数据元素类型

typedef struct
{
	DataType* data;	//数据域
	int curSz;		//元素个数
	int capacity;	//容量
}Heap;

//交换堆两个下标处的数据
void Swap(Heap* hp, int pos1, int pos2)
{
	int tmp = hp->data[pos1];
	hp->data[pos1] = hp->data[pos2];
	hp->data[pos2] = tmp;
}

//堆的向下调整---------建堆时会用到这个接口
//changeSize表示要调整的元素个数
//时间复杂度:O(logN)
void ShiftDown(Heap* hp, int changeSize, int parent)
{
	if (hp == NULL || hp->curSz == 0)
		return;

	int child = parent * 2 + 1;		//左孩子的下标
	while (child < changeSize)
	{
		if (child + 1 < changeSize && hp->data[child + 1] > hp->data[child])
			++child;

		//不符合堆条件就交换数据
		if (hp->data[parent] < hp->data[child])
			Swap(hp, parent, child);
		//符合堆条件就停止调整
		else
			break;

		//继续向下调整
		parent = child;
		child = 2 * parent + 1;
	}
}

//堆的向上调整---堆中插入数据会用到
//changeSize表示要调整的元素个数
//时间复杂度:O(logN)
void ShiftUp(Heap* hp, int changeSize, int child)
{
	if (hp == NULL || hp->curSz == 0)
		return;

	int parent = (child - 1) / 2;	//父节点的下标

	//当child=0时说明了此时已经调整到根节点了
	while (child > 0)
	{
		if (hp->data[parent] < hp->data[child])
			Swap(hp, parent, child);
		else
			break;

		child = parent;
		parent = (child - 1) / 2;
	}
}

//判空
bool IsEmpty(Heap* hp)
{
	if (hp == NULL || hp->curSz == 0)
		return true;
	return false;
}

//判断数据是否已满
bool IsFull(Heap* hp)
{
	if (hp == NULL)
		return false;
	return hp->capacity == hp->curSz;
}

//检查容量
void CheckCapacity(Heap* hp)
{
	if (hp == NULL)
		return;
	if (IsFull(hp))
	{
		int newC = 2 * hp->capacity;
		hp->data = (DataType*)realloc(hp->data, sizeof(DataType)* newC);
		hp->capacity = newC;
	}
}

//插入数据
//时间复杂度:O(logN)
bool HeapPush(Heap* hp, DataType val)
{
	if (hp == NULL)
		return false;

	//检查容量,如果当前的堆空间已满,就开始增容
	CheckCapacity(hp);

	//插入数据并进行向上调整
	hp->data[hp->curSz] = val;
	ShiftUp(hp, hp->curSz + 1, hp->curSz);
	++hp->curSz;

	return true;
}

//删除数据
bool HeapPop(Heap* hp)
{
	if (IsEmpty(hp))
		return false;

	//交换堆的根节点和尾节点的数据
	Swap(hp, 0, hp->curSz - 1);
	//更新元素个数
	--hp->curSz;
	//从根节点开始执行向下调整
	ShiftDown(hp, hp->curSz, 0);
	return true;
}

//建堆
Heap* CreateHeap(DataType* data, int eleSize)
{
	//1.开辟空间
	Heap* hp = (Heap*)malloc(sizeof(Heap));
	hp->curSz = 0;
	hp->capacity = ORIGINSIZE;	//刚开始开辟能存储ORIGINSIZE个元素的堆空间
	hp->data = (DataType*)calloc(hp->capacity, sizeof(DataType));

	//2.插入数据
	//循环了N次(N为数据规模)
	for (int i = 0; i < eleSize; ++i)
	{
		//插入时间复杂度为O(logN)
		HeapPush(hp, data[i]);
	}

	return hp;
}

//销毁堆
void DestroyHeap(Heap* hp)
{
	if (hp == NULL)
		return;
	free(hp->data);
	free(hp);
}

//获取元素个数
int GetHeapSize(Heap* hp)
{
	if (hp == NULL)
		return 0;
	return hp->curSz;
}

//获取堆顶元素
DataType GetHeapTop(Heap* hp)
{
	return hp->data[0];
}

//堆排序
void HeapSort(Heap* hp)
{
	if (IsEmpty(hp))
		return;

	//将头尾节点交换并执行向下调整
	int end = hp->curSz - 1;
	//外循环了N次
	while (end > 0)
	{
		Swap(hp, 0, end);
		//向下调整的时间复杂度为O(logN)
		ShiftDown(hp, end, 0);
		--end;
	}
}

//TopK:打印最大的K个数
void PrintTopK(Heap* hp, int k)
{
	if (IsEmpty(hp))
		return;

	if (k > hp->curSz)
		return;

	//拿到堆顶元素之后并进行删除,执行k次
	while (k--)
	{
		printf("%d ", GetHeapTop(hp));
		HeapPop(hp);
	}
	printf("\n");
}

//清空堆
void ClearHeap(Heap* hp)
{
	if (IsEmpty(hp))
		return;

	hp->curSz = 0;
}

//堆遍历
void HeapTraverse(Heap* hp)
{
	if (IsEmpty(hp))
		return;

	for (int i = 0; i < hp->curSz; ++i)
	{
		printf("%d ", hp->data[i]);
	}
	printf("\n");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值