堆排序是利用二叉树数据结构设计的一种排序算法。
需要注意的是排升序要建大堆,排降序建小堆。
为什么要这样建堆呢?
在初学堆排序时,按照我们自己对二叉树的理解,正常认为,升序建小堆,降序建大堆,但是这是错误的,在解释这个错误之前,我们先了解一下堆排序的思想
思路
堆排序是先要建堆,用向下调整建堆,然后根和最后的叶子结点相交换,交换后,将最后元素固定,使用向下调整选出次大的元素,反复操作。
如图是排降序建小堆的过程
向下调整建堆
向下调整建堆是指根与下面的子节点比较数值交换至适合位置,向下调整建堆的前提是(以小堆为例):根的左子树,右子树都是小堆,这就需要先调整左右子树,而调整左右子树的前提是以左右子树为根的左右子树都是小堆,依次类推,直到叶子节点
先调整粉框树再调整红框树最后调整整个树
//向下调整
void DownHeap(Heap* hp,int n,int i)
{
int parent = i;
int child = parent * 2 + 1;
while(child<n)
{
if (child + 1 < n && hp->_a[child] > hp->_a[child + 1])
{
child++;
}
if (hp->_a[child] < hp->_a[parent])
{
int tmp = hp->_a[child];
hp->_a[child] = hp->_a[parent];
hp->_a[parent] = tmp;
}
else
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(Heap* hp, int n)
{
//向下调整建堆 //倒着向下建堆;
for (int i =(n-1-1)/2; i >= 0; i--) //n-1表示数组最后一个元素;
{ //(n-1-1)/2是表示叶子节点的父节点;
DownHeap(hp, n, i); //子节点和父节点的关系是:parent = (child-1)/2;
}
}
纠错
如果升序建小堆,降序建大堆,(小堆升序为例),选出最小的数据后,排在根的位置,之后剩下的数据需要再看做堆,这样父子关系全乱了,都需要重新建新的堆,代价太大!!!
找到了最小值1,排入根的位置
父子关系全乱,需要重新建堆(这个例子已是升序,只是巧合!)
继续
建立堆后,我们已经得到最小的数在根处,之后要和最后的叶子节点交换位置,再向下调整找出次小的数移至根处,向下调整时,把之前最小的数忽视掉,那个数现在的位置已经是他的最终位置,不需要再动
int end = n - 1;
while (end > 0)
{
int tmp = hp->_a[end];
hp->_a[end] = hp->_a[0];
hp->_a[0] = tmp;
DownHeap(hp,end,0);
end--; //控制与根交换后的最小数不参与调整
}
代码
void DownHeap(Heap* hp,int n,int i)
{
int parent = i;
int child = parent * 2 + 1;
while(child<n)
{
if (child + 1 < n && hp->_a[child] > hp->_a[child + 1])
{
child++;
}
if (hp->_a[child] < hp->_a[parent])
{
int tmp = hp->_a[child];
hp->_a[child] = hp->_a[parent];
hp->_a[parent] = tmp;
}
else
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
void HeapSort(Heap* hp, int n)
{
//建堆
for (int i =(n-1-1)/2; i >= 0; i--)
{
DownHeap(hp, n, i);
}
int end = n - 1;
while (end > 0)
{
int tmp = hp->_a[end];
hp->_a[end] = hp->_a[0];
hp->_a[0] = tmp;
DownHeap(hp,end,0);
end--;
}
}
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定