【数据结构】堆的基本操作

  堆,实际上就是一棵完全二叉树,本篇正是利用这点实现了堆的基本操作,包括构造堆,插入数据,删除堆顶数据,获取堆顶数据,判空,排序以及查找海量数据中最小的前k个数据等等。

头文件

heap.h

# ifndef __HEAP_H__
# define __HEAP_H__

# include <stdio.h>
# include <assert.h>
# include <stdlib.h>
# include <string.h>

# define MAX_SIZE (20)

typedef int DataType;

typedef struct Heap
{
	DataType data[MAX_SIZE];
	int size;
}Heap;

void HeapInit(Heap * pH, DataType data[], int size); // 堆的初始化
void AdjustDown(Heap * pH, int root); // 向下调整堆
void AdjustUp(Heap * pH, int child); // 向上调整堆
void MakeHeap(Heap * pH); // 构造堆
void HeapPush(Heap * pH, DataType data); // 插入一个数据,保证堆的性质仍然满足
void HeapPop(Heap * pH); // 删除堆顶数据,保证堆的性质仍然满足
DataType * FindTopK(DataType data[], int size, int k); // 查找海量数据中最小的前K个数据
int HeapTop(Heap * pH); // 获取堆顶数据
void HeapSort(DataType data[], int size); // 堆排序
void DataPrint(DataType data[], int size); // 打印数组元素
int HeapIsEmpty(Heap * pH); // 判断堆是否为空,返回1表示堆为空,返回0表示堆不为空
int HeapSize(Heap * pH); // 堆的大小
void HeapDestory(Heap * pH); // 堆的销毁

# endif // __HEAP_H__

难点剖析

1.堆排序

  a.从小到大排序构造大堆,然后两两交换(ps:交换首元素与最后一个元素),每一次交换后向下调整,保持大堆的状态,这样就可以依次得到最大的元素、次大的元素...

  b.从大到小排序构造小堆,然后两两交换(ps:交换首元素与最后一个元素),每一次交换后向下调整,保持小堆的状态,这样就可以依次得到最小的元素、次小的元素...

2.删除堆顶元素

  用最后一个元素去替换首元素,这样就相当于减少了一个元素,然后再向下调整,保持小(大)堆的状态,这样就实现了删除堆顶元素。

3.查找海量数据中最小的前k个数据

  首先构造包含k个数据的大堆,然后从海量数据中的第k+1个数据开始,依次与堆中数据的最大值,也就是与堆顶元素进行比较,如果比堆顶元素小,那么就交换两者的数据,并不断向下调整,直到找到最小的前k个数据。

源代码

1.heap.c

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 文件名称:heap.c
* 功能:堆的基本操作内部实现细节
*
* 当前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月20日10:35:44
*/

# include "heap.h"

/*
*	函数名称:HeapInit
*
*	函数功能:堆的初始化
*
*	入口参数:pH, data, size
*
*	出口参数:void
*
*	返回类型:void
*/

void HeapInit(Heap * pH, DataType data[], int size)
{
	assert(NULL != pH);
	assert(size <= MAX_SIZE);

	pH->size = size;

	memcpy(pH->data, data, sizeof(DataType) * size);

	return;
}

/*
*	函数名称:Swap
*
*	函数功能:交换数据
*
*	入口参数:a, b
*
*	出口参数:void
*
*	返回类型:void
*/

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

	return;
}

/*
*	函数名称:AdjustDown
*
*	函数功能:向下调整堆(递归)
*
*	入口参数:pH, root
*
*	出口参数:void
*
*	返回类型:void
*/

//void AdjustDown(Heap * pH, int root)
//{
//	int minChild = 0;
//	int left = root * 2 + 1;
//	int right = root * 2 + 2;
//
//	assert(NULL != pH);
//	
//	//越界
//	if (root >= pH->size)
//	{
//		return;
//	}
//	else if (left >= pH->size)
//		{
//			return;
//		}
//		else
//		{
//			;
//		}
//	//假设左孩子小,如果左孩子大的话会在if判断语句中进行调整
//	minChild = left;
//
//	//确保右孩子存在,并且右孩子比左孩子小
//	//right最大即为数组最后一个下标,其小于pH->size
//	if ((right < pH->size) && (pH->data[right] < pH->data[left]))
//	{
//		minChild = right;
//	}
//	else
//	{
//		;
//	}
//
//	if (pH->data[minChild] < pH->data[root])
//	{
//		Swap(pH->data + minChild, pH->data + root);
//		AdjustDown(pH, minChild);
//	}
//	else
//	{
//		;
//	}
//
//	return;
//}

/*
*	函数名称:AdjustDown
*
*	函数功能:向下调整堆(循环)
*
*	入口参数:pH, root
*
*	出口参数:void
*
*	返回类型:void
*/

void AdjustDown(Heap * pH, int root)
{
	int minChild = 0;
	int parent = root;
	int left = 0;
	int right = 0;

	assert(NULL != pH);
	
	//越界
	while (parent < pH->size)
	{
		left = parent * 2 + 1;
		right = parent * 2 + 2;

		if (left >= pH->size)
		{
			return;
		}
		else
		{
			;
		}
		//假设左孩子小,如果左孩子大的话会在if判断语句中进行调整
		minChild = left;

		//确保右孩子存在,并且右孩子比左孩子小
		//right最大即为数组最后一个下标,其小于pH->size
		if ((right < pH->size) && (pH->data[right] < pH->data[left]))
		{
			minChild = right;
		}
		else
		{
			;
		}

		if (pH->data[minChild] > pH->data[parent])
		{
			return;
		}
		else
		{
			Swap(pH->data + minChild, pH->data + parent);
			parent = minChild;
		}
	}

	return;
}

/*
*	函数名称:AdjustUp
*
*	函数功能:向上调整堆(递归)
*
*	入口参数:pH, child
*
*	出口参数:void
*
*	返回类型:void
*/

//void AdjustUp(Heap * pH, int child)
//{
//	int parent = (child - 1) / 2;
//
//	assert(NULL != pH);
//
//	//与根比较
//	if(pH->data[child] > pH->data[parent])
//	{
//		//满足堆
//		return;
//	}
//	else
//	{
//		Swap(pH->data + child, pH->data + parent);
//	}
//
//	//已经调整到根结点
//	if (0 == parent)
//	{
//		return;
//	}
//	else
//	{
//		AdjustUp(pH, parent);
//	}
//
//	return;
//}

/*
*	函数名称:AdjustUp
*
*	函数功能:向上调整堆(循环)
*
*	入口参数:pH, child
*
*	出口参数:void
*
*	返回类型:void
*/

void AdjustUp(Heap * pH, int child)
{
	int parent = 0;

	assert(NULL != pH);

	while (1)
	{
		parent = (child - 1) / 2;

		//插入结点在右子树,并且右子树大于左子树
		if ((0 == child % 2) && (pH->data[child] > pH->data[child - 1]))
		{
			//当前结点所在子树已经满足堆
			return;
		}
		else
		{
			;
		}

		//与根比较
		if(pH->data[child] > pH->data[parent])
		{
			//满足堆
			return;
		}
		else
		{
			Swap(pH->data + child, pH->data + parent);
		}

		//已经调整到根结点
		if (0 == parent)
		{
			return;
		}
		else
		{
			child = parent;
		}
	}

	return;
}

/*
*	函数名称:AdjustSortDown
*
*	函数功能:向下调整堆(递归) 大堆
*
*	入口参数:data, size, root
*
*	出口参数:void
*
*	返回类型:void
*/

void AdjustSortDown(DataType data[], int size, int root)
{
	int maxChild = 0;
	int left = root * 2 + 1;
	int right = root * 2 + 2;
	
	//越界
	if (root >= size)
	{
		return;
	}
	else if (left >= size)
		{
			return;
		}
		else
		{
			;
		}
	//假设左孩子大,如果左孩子小的话会在if判断语句中进行调整
	maxChild = left;

	//确保右孩子存在,并且右孩子比左孩子大
	//right最大即为数组最后一个下标,其大于size
	if ((right < size) && (data[right] > data[left]))
	{
		maxChild = right;
	}
	else
	{
		;
	}

	if (data[maxChild] > data[root])
	{
		Swap(data + maxChild, data + root);
		AdjustSortDown(data, size, maxChild);
	}
	else
	{
		;
	}

	return;
}

/*
*	函数名称:MakeHeap
*
*	函数功能:构造堆
*
*	入口参数:pH
*
*	出口参数:void
*
*	返回类型:void
*/

void MakeHeap(Heap * pH)
{
	int i = 0;
	int minChild = 0;
	int left = 0;
	int right = 0;

	assert(NULL != pH);
	
	//最后一个结点是size-1,那么最后一个结点的双亲结点是((size-1)-1)/2
	for (i = (pH->size - 2) / 2; i >= 0; i--)
	{
		left = i * 2 + 1;
		right = i * 2 + 2;
		//假设左孩子小,如果左孩子大的话会在if判断语句中进行调整
		minChild = left;

		//确保右孩子存在,并且右孩子比左孩子小
		//right最大即为数组最后一个下标,其小于pH->size
		if ((right < pH->size) && (pH->data[right] < pH->data[left]))
		{
			minChild = right;
		}
		else
		{
			;
		}

		if (pH->data[i] > pH->data[minChild])
		{
			Swap(pH->data + i, pH->data + minChild);
			AdjustDown(pH, minChild);
		}
		else
		{
			;
		}
	}

	return;
}

/*
*	函数名称:HeapPush
*
*	函数功能:插入一个数据,保证堆的性质仍然满足
*
*	入口参数:pH, data
*
*	出口参数:void
*
*	返回类型:void
*/

void HeapPush(Heap * pH, DataType data)
{
	assert(NULL != pH);
	assert(pH->size < MAX_SIZE);

	//尾插
	pH->data[pH->size++] = data;
	AdjustUp(pH, pH->size - 1);

	return;
}

/*
*	函数名称:HeapPop
*
*	函数功能:删除堆顶数据,保证堆的性质仍然满足
*
*	入口参数:pH
*
*	出口参数:void
*
*	返回类型:void
*/

void HeapPop(Heap * pH)
{
	assert(NULL != pH);
	assert(pH->size > 0);

	//用数组中的最后一个元素去替换首元素,相当于减少一个元素,再向下调整
	pH->data[0] = pH->data[pH->size - 1];
	AdjustDown(pH, 0);

	return;
}

/*
*	函数名称:HeapSort
*
*	函数功能:堆排序
*
*	入口参数:data, size
*
*	出口参数:void
*
*	返回类型:void
*/

void HeapSort(DataType data[], int size)
{
	//建大堆
	int i = 0;
	int left = 0;
	int right = 0;
	int maxChild = 0;

	for (i = (size - 2) / 2; i >= 0; i--)
	{
		left = i * 2 + 1;
		right = i * 2 + 2;
		//假设左孩子大
		maxChild = left;

		//右孩子存在,并且右孩子大于左孩子
		if ((right < size) && (data[right] > data[left]))
		{
			maxChild = right;
		}
		else
		{
			;
		}

		if (data[maxChild] > data[i])
		{
			Swap(data + maxChild, data + i);
			AdjustSortDown(data, size, maxChild);
		}
		else
		{
			;
		}
	}

	//建好堆后
	for (i = 0; i < size; i++)
	{
		Swap(data, data + size - 1 - i);
		AdjustSortDown(data, size - 1 - i, 0);
	}

	return;
}

/*
*	函数名称:DataPrint
*
*	函数功能:打印数组中的元素
*
*	入口参数:data, size
*
*	出口参数:void
*
*	返回类型:void
*/

void DataPrint(DataType data[], int size)
{
	int i = 0;

	for (i = 0; i < size; i++)
	{
		printf("%d ", data[i]);
	}

	printf("\n");

	return;
}

/*
*	函数名称:HeapTop
*
*	函数功能:获取堆顶数据
*
*	入口参数:pH
*
*	出口参数:pH->data[0]
*
*	返回类型:int
*/

int HeapTop(Heap * pH)
{
	assert(NULL != pH);
	assert(pH->size > 0);

	return pH->data[0];
}

/*
*	函数名称:FindTopK
*
*	函数功能:查找海量数据中最小的前K个数据
*
*	入口参数:data, size, k
*
*	出口参数:newdata
*
*	返回类型:DataType *
*/

DataType * FindTopK(DataType data[], int size, int k)
{
	int i = 0;
	int left = 0;
	int right = 0;
	int maxChild = 0;

	DataType * newdata = (DataType *)malloc(sizeof(DataType) * k);

	assert(NULL != newdata);

	memcpy(newdata, data, sizeof(DataType) * k);

	//在newdata上构造大堆,并且堆中有k个数据
	for (i = (k - 2) / 2; i >= 0; i--)
	{
		left = i * 2 + 1;
		right = i * 2 + 2;
		//假设左孩子大
		maxChild = left;

		//右孩子存在,并且右孩子大于左孩子
		if ((right < k) && (newdata[right] > newdata[left]))
		{
			maxChild = right;
		}
		else
		{
			;
		}

		if (newdata[maxChild] > newdata[i])
		{
			Swap(newdata + maxChild, newdata + i);
			AdjustSortDown(newdata, k, maxChild);
		}
		else
		{
			;
		}
	}

	//堆构造成功
	for (i = k; i < size; i++)
	{
		//由于构造的是大堆,因此查找的原始数组中的数据要比堆中最大的数据小
		if (data[i] < newdata[0])
		{
			newdata[0] = data[i];
			AdjustSortDown(newdata, k, 0);
		}
		else
		{
			;
		}
	}
	
	return newdata;
}

/*
*	函数名称:HeapIsEmpty
*
*	函数功能:判断堆是否为空,返回1表示堆为空,返回0表示堆不为空
*
*	入口参数:pH
*
*	出口参数:1 or 0
*
*	返回类型:int
*/

int HeapIsEmpty(Heap * pH)
{
	assert(NULL != pH);

	return 0 == pH->size ? 1 : 0;
}

/*
*	函数名称:HeapSize
*
*	函数功能:堆的大小
*
*	入口参数:pH
*
*	出口参数:pH->size
*
*	返回类型:int
*/

int HeapSize(Heap * pH)
{
	assert(NULL != pH);

	return pH->size;
}

/*
*	函数名称:HeapDestory
*
*	函数功能:堆的销毁
*
*	入口参数:pH
*
*	出口参数:void
*
*	返回类型:void
*/

void HeapDestory(Heap * pH)
{
	assert(NULL != pH);

	pH->size = 0;
	
	return;
}

2.test.c

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 文件名称:test.c
* 功能:测试堆的基本操作
*
* 当前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月20日10:36:37
*/

# include "heap.h"

/*
*	函数名称:main
*
*	函数功能:测试主程序
*
*	入口参数:void
*
*	出口参数:0
*
*	返回类型:int
*/

int main(void)
{
	Heap heap;
	int i = 0;
	int * newdata = NULL;

	DataType data[] = {12, 10, 8, 9, 2, 11, 1};

	HeapInit(&heap, data, sizeof(data) / sizeof(DataType));
	MakeHeap(&heap);

	printf("插入数据之前,堆顶元素为: %d\n", HeapTop(&heap));
	HeapPush(&heap, 0);
	printf("插入数据之后,堆顶元素为: %d\n", HeapTop(&heap));
	HeapPop(&heap);
	printf("删除堆顶数据之后,堆顶元素为: %d\n", HeapTop(&heap));

	newdata = FindTopK(data, sizeof(data) / sizeof(DataType), 3);

	printf("数组中最小的前3个数分别是:\n");

	for (i = 0; i < 3; i++)
	{
		printf("%d\t", newdata[i]);
	}

	printf("\n排序之前:\n");
	DataPrint(data, sizeof(data) / sizeof(DataType));

	HeapSort(data, sizeof(data) / sizeof(DataType));

	printf("排序之后:\n");
	DataPrint(data, sizeof(data) / sizeof(DataType));

	HeapDestory(&heap);

	return 0;
}

输出结果


猜你喜欢

转载自blog.csdn.net/sustzc/article/details/80793521