如何建堆? 如何掌握堆排序? 轻松搞定。

前言

堆排序是一种效率非常高的排序算法,时间复杂度为O(N * log2 N)。想要利用堆对数据进行排序首先要了解堆的特性。



堆的逻辑结构为一棵完全二叉树,但实际上在内存中存储的是一个数组。堆是一颗顺序存储的完全二叉树。

在这里插入图片描述



大堆和小堆


堆分为大堆和小堆。

  • 大堆的每个非叶子结点都比其子节点大。
  • 小堆的每个非叶子结点都比其子节点小。
    在这里插入图片描述



堆的性质

已知双亲节点的下标为 i ,其孩子节点的下标为:

  • 左孩子结点下标: 2 * i + 1
  • 右孩子结点下标: 2 * i + 2

在这里插入图片描述

已知孩子结点下标为i ,其双亲节点下标为: (i - 1)/ 2
在这里插入图片描述




如何建堆?


向下调整算法/向上调整算法是建堆的关键,想要对一个无序的数组排序,这个数组(逻辑上的完全二叉树)必须是一个大堆或者是一个小堆,首先先介绍向下调整算法

向下调整算法建堆


如图,以下二叉树经过以下步骤转换为大堆:

  1. 选出孩子节点中大的值大的与父结点比较,如孩子节点大于父结点则交换,如小于则停止。
  2. 持续向下比较,比较到叶子结点或者孩子结点小于父结点则停止。

在这里插入图片描述

此时这颗完全二叉树已经是一个大堆。


但是有一个限制,这颗完全二叉树的左子树和右子树必须是大堆才能通过一次向下调整算法调整为大堆。

在这里插入图片描述

但如下图,此时这个数组无序就无法这样建堆了。

在这里插入图片描述

”从后向前建堆”

找到数组最后一个元素的父节点,为图中编号为1的子树,从后向前执行向下调整算法。

在这里插入图片描述

调整完毕,此时此二叉树已成为一个大堆。

在这里插入图片描述

代码示例:

//向下调整算法 大堆
void AdjustDown(int* nums, int numSize, int parent)
{
    
    
	//找到左孩子
	int child = parent * 2 + 1;
	while (child < numSize)
	{
    
    
		//找出最大孩子。    (child + 1 < numSize) 如最后一颗子树只有一个孩子节点则会越界
		if ((child + 1 < numSize) && nums[child + 1] > nums[child]) 
			child++;

		if (nums[child] > nums[parent])
		{
    
    
			//交换父结点和孩子结点
			swap(&nums[child], &nums[parent]);
			//继续向下比较
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
    
    
			//孩子节点小于父结点就停止交换
			break;
		}
	}
}


void HeapCreate(int arr[], int sz)

	int i;
	// 子结点下标为i 其父结点下标为(i - 1)/2
	for (i = (sz - 1 - 1) / 2; i >= 0; i--)
		AdjustDown(arr, sz, i);
}

运行结果

在这里插入图片描述



向上调整算法建堆

向上调整算法建堆的过程与向下调整算法相似,从下标为1的结点开始向后调整:

  1. 如孩子节点大于父结点就进行交换,直到与根结点比较完毕。
  2. 如孩子节点小于父节点也停止调整,让下一结点进行调整。

在这里插入图片描述

代码示例:

如果对建堆的过程有疑惑多在纸上模拟其过程就很清楚了。

void AdjustUp(DataType* nums, int child)
{
    
    
	assert(nums);
	int parent = (child - 1) / 2;

	while (child > 0)
	{
    
    
		if (nums[child] > nums[parent])
		{
    
    
			Swap(&nums[child], &nums[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
    
    
			break;
		}
	}
}

void HeapCreate(int arr[], int sz)
{
    
    
	int i;
	//从下标为1的结点向后进行向上调整算法建堆
	for (i = 1; i < sz; i++)
		AdjustUp(arr, i);

}

运行结果

在这里插入图片描述





利用建好的堆排序


此时已经得到了一个大堆,排序过程如下图。

在这里插入图片描述

可以发现,排升序应使用大堆,降序应使用小堆。



代码示例


void HeapSort(int arr[], int sz)
{
    
    

	int i;
	for (i = sz - 1; i > 0; i--)
	{
    
    
		Swap(&arr[0], &arr[i]);
		sz--;
		AdjustDown(arr, sz, 0);
	}

}

int main()
{
    
    
	int arr[9] = {
    
     15,22,7,88,12,16,77,1,67 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	HeapCreate(arr, sz);
	HeapSort(arr, sz);

	int i;
	for (i = 0; i < sz; i++)
		printf("%d ", arr[i]);

}

猜你喜欢

转载自blog.csdn.net/juggte/article/details/120137969