堆的经典问题(建堆算法、堆排序、topK问题)

一、建堆算法

  将一组数据转变为符合堆特性的另一组数据,通俗来说就是将数组变为堆

1.向上调整建堆

1.1思想

  将原数据从第一个元素开始逐一向上调整,假设原数据个数为n,则向上调整n次,类似于将原数据逐一插入到原数据当中

1.2画图

向上调整建堆

1.3代码

//交换数据
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整
void AdjustUp(int* pa, int child)
{
	assert(pa);

	int parent = (child - 1) / 2;

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

//向上调整建堆
void CreateHeapUp(int* arr, int n)
{
	assert(arr);

	//从第一个元素开始逐一向上调整
	for (int i = 0; i < n; i++)
	{
		AdjustUp(arr, i);
	}
}

1.4复杂度

向上调整建堆时间复杂度

时间复杂度:O(nlog2n)
空间复杂度:O(1)

2.向下调整建堆

2.1思想

  先调整小树,再调整大树。从原数据的下标最大的的分支结点开始进行向下调整父结点直至下标最小的分支结点。树的分支结点下标<=(n-1)/2

2.2画图

向下调整建堆

2.3代码

//交换数据
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向下调整
void AdjustDown(int* pa, int n, int parent)
{
	assert(pa);

	int child = parent * 2 + 1;

	/*while (parent <= (n-1) / 2)*/
	while (child < n)
	{
		if (child + 1 < n && pa[child + 1] > pa[child])
			child++;

		if (pa[child] > pa[parent])
		{
			Swap(&pa[child], &pa[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//向下调整建堆
void CreateHeapDown(int* arr, int n)
{
	assert(arr);

	//从最下标最大的分支结点开始向下调整直至下标最小的分支结点
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}
}

2.4复杂度

向下调整建堆时间复杂度

时间复杂度:O(n)
空间复杂度:O(1)

3.知识补充

大根堆:双亲结点(父结点)大于等于孩子结点
小根堆:双亲结点(父结点)小于等于孩子结点
大根堆小根堆建堆区别:在调整函数改变>或者<符号即可实现彼此

以大根堆为例
向上调整函数(AdjustUp):
时间复杂度:O(log2n) = 树高
功能:调整一棵树为堆
特点:因为叫做向上调整,所以调整下面,一般从最后一个结点开始向上调整,孩子与父亲比较,若孩子权值大孩子上移
向下调整函数(AdjustDown):
时间复杂度:O(n) = 树高
功能:调整一棵树为堆
特点:因为叫做向下调整,所以调整上面,一般从第一个结点开始向下调整,父亲与孩子比较,若孩子权值大父亲下移

所以,我们一般采用向下调整建堆。

二、堆排序

  利用堆这种数据结构将一组数据升序或降序,升序建大堆,降序建小堆

1.升序

1.1思想

  先建立大根堆,然后交换堆顶元素和堆底元素,再将堆的长度减1,直至堆中只有一个元素。

1.2画图

升序

1.3代码

//堆排序(升序)
void HeapSortUp(int* arr, int n)
{
	//向下调整建大堆
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}
}

1.4复杂度

时间复杂度:O(nlog2n)
空间复杂度:O(1)

2.降序

2.1思想

  先建立小根堆,然后交换堆顶元素和堆底元素,再将堆的长度减1,直至堆中只有一个元素。

2.2画图

降序

2.3代码

//堆排序(降序)
void HeapSortDown(int* arr,int n)
{
	//向下调整建小堆
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}
}

2.4复杂度

时间复杂度:O(nlog2n)
空间复杂度:O(1)

三、topK问题

  给一个长度为N无序的数组, 请输出最小 (或最大)的K个数。

1.最大的前K个

1.1思想

  将N的前K个建一个大小为K的小堆,从N的第K+1元素开始与堆顶元素比较,若原数据的第K+1个元素大于堆顶元素就覆盖该堆顶元素,然后调整该堆;否则就遍历原数据的下一个元素。直至遍历完原数据的所有元素。

1.2画图

最大的前K个

1.3代码

//topK问题(最大的前K个)
void GetTopK1(int* arr, int n,int K)
{
	//将原数据的前K个建小堆
	for (int i = (K - 1) / 2; i >= 0; i--)
	{
		//向下调整建堆
		AdjustDown(arr, K, i);
	}

	//从原数据第K+1个元素开始遍历原数据
	for (int i = K; i < n; i++)
	{
		//如果当前元素大于堆顶元素
		if (arr[i] > arr[0])
		{
			//则覆盖堆顶元素
			arr[0] = arr[i];
			//调整新堆
			AdjustDown(arr, K, 0);
		}
	}
}

1.4复杂度

时间复杂度:O(Nlog2K)
空间复杂度:O(1)

2.最小的前K个

2.1思想

  将N的前K个建一个大小为K的大堆,从N的第K+1元素开始与堆顶元素比较,若原数据的第K+1个元素小于堆顶元素就覆盖该堆顶元素,然后调整该堆;否则就遍历原数据的下一个元素。直至遍历完原数据的所有元素。

2.2画图

最小的前K个

2.3代码

//topK问题(最小的前K个)
void GetTopK2(int* arr, int n,int K)
{
	//将原数据的前K个建大堆
	for (int i = (K - 1) / 2; i >= 0; i--)
	{
		//向下调整建堆
		AdjustDown(arr, K, i);
	}

	//从原数据第K+1个元素开始遍历原数据
	for (int i = K; i < n; i++)
	{
		//如果当前元素小于堆顶元素
		if (arr[i] < arr[0])
		{
			//则覆盖堆顶元素
			arr[0] = arr[i];
			//调整新堆
			AdjustDown(arr, K, 0);
		}
	}
}

2.4复杂度

时间复杂度:O(Nlog2K)
空间复杂度:O(1)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值