欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

最大堆、最小堆的构造,利用堆实现数组排序的C++代码

程序员文章站 2024-02-13 23:18:10
...

一、什么是堆?

堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;

  • 堆总是一棵完全二叉树。

简单点说堆就是满足特定条件的完全二叉树,这里的“特定条件”分两种:即要么这棵树所有的节点都不大于(小于等于)其根节点(如果有)的值,如下图所示就称为最大堆
最大堆、最小堆的构造,利用堆实现数组排序的C++代码

要么这棵树所有的节点都不小于(大于等于)其根节点的值,如下图所示的称为最小堆

最大堆、最小堆的构造,利用堆实现数组排序的C++代码
一句话,对于一颗完全二叉树,根节点总最大时叫最大堆,根节点总最小时叫最大堆。

二、如何构造一个堆

以构建最大堆为例,为了方便起见,不妨用一个一维数组来表示一颗完全二叉树,例如:

int heap[] = { 16,4,10,14,7,9,3,2,8,1 };

已知heap是一个完全二叉树,自然能够画出它的等效结构:

最大堆、最小堆的构造,利用堆实现数组排序的C++代码
显然heap不是最大堆,为了使其满足最大堆结构,我们需要对二叉树的结构进行调整。

为了保证每次调整都向最大堆的目标更进一步,我们应当自下而上对节点调整。具体思路是:对于除了叶子节点外的任一节点A,假设其两个孩子节点分别为B、C,如果A的值不是最大的,则将节点A和最大的那个节点(B或者C)交换。考虑到A节点被下放之后可能会影响已经调整好的子树,再递归一下就好了。我们从右至左,自下而上的重复这个操作,即可构建最大堆。

  1. 第一个小目标:实现一颗最小子树的调整。需要注意的细节是倘若已知根节点下标,如何表示其左孩子和右孩子下标。第三个参数heap_size可以暂时理解为数组的长度,接下来会提到。
void MaxHeapIFY(int *heap,int index,int heap_size)
{//寻找当前节点和其左右孩子中的最大值并交换,因为实际是自下而上操作,所以默认当前节点的左右子树都已是最大堆
	int left = 2 * index + 1;
	int right = left + 1;
	int largest = index;
	if (left < heap_size && heap[left]>heap[index]) // not out of index
		largest = left;
	if (right<heap_size && heap[right]>heap[largest])
		largest = right;
	if (largest != index) // 交换最大节点
	{
		int temp = heap[largest];
		heap[largest] = heap[index];
		heap[index] = temp;
		//如果当前堆发生改变,则递归地操作后续堆以适应最大堆结构
		MaxHeapIFY(heap, largest, heap_size);
	}
}

2.对除了叶子结点外的所有节点执行调整操作,这里需要注意的细节是叶子结点在数组中的下标为heap_size/2-1

void BuildMaxHeap(int* heap,int heap_size)
{
	for (int i = (heap_size) / 2 - 1; i >= 0; i--)
	{
		MaxHeapIFY(heap, i,heap_size);
	}
}

然后在主函数中进行如下操作,就将heap成功调整为最大堆结构了~

BuildMaxHeap(heap, sizeof(heap)/sizeof(heap[0]));

调整后的模型如下图:
最大堆、最小堆的构造,利用堆实现数组排序的C++代码

三、堆排序

倘若我们已经成功将一个数组构造成最大堆(最小堆),那么对数组进行排序这件事就如同探囊取物了——我们每次将数组的第一个元素摘出放到末尾,及时调整堆的结构,就可以实现排序啦!那么我们如何保证已经被摘出的元素不会再参与新的调整呢?这件事是heap_size完成的,每次我们取出最大元素后就让heap_size-1就OK了!

void HeapSort(int* heap, int heap_size)
{
	BuildMaxHeap(heap, heap_size); //heap[0]恒为当前需调整的元素,将其与heap[final]交换并及时调整堆
	for (int final = heap_size-1; final > 0; final--)
	{
		int tmp = heap[0];
		heap[0] = heap[final]; //将堆顶最大元素摆放正确
		heap[final] = tmp;
		MaxHeapIFY(heap, 0, final); //重新调整最大堆,同时heap_size(也就是final的值)在不断变小
	}
}

有了最大堆,自然不难实现最小堆,感兴趣的可以自己实现一下。

除了排序,堆还可以进行插入元素、删除元素、增大元素值等等,就不多介绍了,代码直接放在下面。发blog记录一下学习数据结构&算法的细节,希望共同进步。

#include <iostream>
#include<limits>
using namespace std;
/* heap: An array conclude elements of the HEAP 
   index:current index for operation
   heap_size: max size of the heap
   return: None
   left:2*index + 1 (if exist)
   right:2*index + 2(if exist)
*/ 
void MaxHeapIFY(int *heap,int index,int heap_size)
{//寻找当前节点和其左右孩子中的最大值并交换,因为实际是自下而上操作,所以默认当前节点的左右子树都已是最大堆
	int left = 2 * index + 1;
	int right = left + 1;
	int largest = index;
	if (left < heap_size && heap[left]>heap[index]) // not out of index
		largest = left;
	if (right<heap_size && heap[right]>heap[largest])
		largest = right;
	if (largest != index) // 交换最大节点
	{
		int temp = heap[largest];
		heap[largest] = heap[index];
		heap[index] = temp;
		//如果当前堆发生改变,则递归地操作后续堆以适应最大堆结构
		MaxHeapIFY(heap, largest, heap_size);
	}
}
void MinHeapIFY(int* heap, int index, int heap_size)
{
	int left = 2 * index + 1;
	int right = left + 1;
	int minindex = index;
	if (left < heap_size && heap[left] < heap[index])
		minindex = left;
	if (right < heap_size && heap[right] < heap[minindex])
		minindex = right;
	if (minindex != index)
	{
		int temp = heap[minindex];
		heap[minindex] = heap[index];
		heap[index] = temp;
		MinHeapIFY(heap, minindex, heap_size);
	}
}
// 自下而上对除叶子节点外的节点进行调整
void BuildMaxHeap(int* heap,int heap_size)
{
	for (int i = (heap_size) / 2 - 1; i >= 0; i--)
	{
		MaxHeapIFY(heap, i,heap_size);
	}
}
void BuildMinHeap(int* heap, int heap_size)
{
	for (int i = (heap_size) / 2 - 1; i >= 0; i--)
	{
		MinHeapIFY(heap, i, heap_size);
	}
}
//根据最大堆结构对数组进行堆排序
void HeapSort(int* heap, int heap_size)
{
	BuildMaxHeap(heap, heap_size); //heap[0]恒为当前需调整的元素,将其与heap[final]交换并及时调整堆
	for (int final = heap_size-1; final > 0; final--)
	{
		int tmp = heap[0];
		heap[0] = heap[final]; //将堆顶最大元素摆放正确
		heap[final] = tmp;
		MaxHeapIFY(heap, 0, final); //重新调整最大堆
	}
}
//最大堆应用:优先队列
//HeapMaximum:返回堆中最大元素
//Insert:把元素插入集合S中
//ExtraMax:去掉最大元素并返回
//IncreaseKey(heap,index,k)将元素head[index]关键值增加到k
int Heap_Maximum(int* heap)
{
	return heap[0];
}
//将heap[index]增加为key,关键在于增加值后维持最大堆的结构
int HeapIncreaseKey(int* heap, int index, int key)
{
	if (heap[index] > key)
		return -1;
	heap[index] = key;//key是一个更大的数,所以我们只需关注key和父节点的大小关系
	while (index != 0 && heap[index] > heap[(index + 1) / 2 - 1])
	{//与父节点交换并继续向上寻找,直到找到根节点
		int temp = heap[(index + 1) / 2 - 1];
		heap[(index + 1) / 2 - 1] = heap[index];
		heap[index] = temp;
		index = (index + 1) / 2 - 1;
	}
	return 0;
}
int Heap_Insert(int* heap,int *heap_point, int key)
{
	*heap_point += 1; //指针传递修改堆有效值
	HeapIncreaseKey(heap, *heap_point, key); //将一个INT_MIN修改为key
	return 0;
}
int Heap_ExtraMax(int* heap,int *heap_point)
{
	if (*heap_point == 0)
		return -1;
	int maxvalue = heap[0]; 
	heap[0] = heap[(*heap_point)-1]; //堆尾元素覆盖堆顶
	heap[(*heap_point) - 1] = INT_MIN; //堆尾元素直接覆盖
	(*heap_point)--; //更新堆元素个数
	MaxHeapIFY(heap, 0,*heap_point); //将整个堆重新调整
	return maxvalue;
}
int main()
{
	int heap[] = { 16,4,10,14,7,9,3,2,8,1,INT_MIN,INT_MIN }; // INT_MIN 无穷小,占位以便插入或删除
	int heap_size = 0;
	for(int i:heap)
	{	
		if (i != INT_MIN)
			heap_size += 1;
	}
	cout << "Init Heap:" << endl;
	for (int i : heap)
		cout << i << " ";
	cout <<endl<< "Sorted Heap:" << endl;
	BuildMaxHeap(heap, heap_size);
	//HeapIncreaseKey(heap, 1, 20);
	//Heap_Insert(heap, &heap_size, 20);
	//int maxva = Heap_ExtraMax(heap, &heap_size);
	//cout << "已删除堆顶元素:" << maxva << endl;
	for (int i : heap)
		cout << i << " ";
	cout <<endl<<"堆大小"<< heap_size << endl;
	return 0;		
}