目录
一、建堆算法
将一组数据转变为符合堆特性的另一组数据,通俗来说就是将数组变为堆
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画图
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画图
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)