堆排序介绍
我们知道,堆分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。鉴于最大堆和最小堆是对称关系,理解其中一种即可。本文将对最大堆实现的升序排序进行详细说明。
最大堆进行升序排序的基本思想:
- 初始化堆:将数列a[0…n-1]构造成最大堆。
- 交换数据:将a[0]和a[n-1]交换,使a[n]是a[0…n-1]中的最大值;然后将a[0…n-2]重新调整为最大堆。 接着,将a[0]和a[n-2]交换,使a[n-2]是a[0…n-2]中的最大值;然后将a[0…n-3]重新调整为最大值。 依次类推,直到整个数列都是有序的。
堆排序图文说明
1、初始化堆
基本思想:假设存在n个数据的无序数组a[0…n-1],其左右子节点对于父节点i的索引分别是2 * i+1和2 * i+2,则最后一个父节点的索引为(n-1)/2,以此父节点为基准,采用堆的向下调整算法,从后往前遍历,这样就可以将无序数组转化为二叉堆。
for (int i = size / 2-1; i >= 0; i--)
{
maxHeapDown(vec, i, size - 1); //最大堆的向下调整算法
}
详细图文介绍见参考链接
2、交换数据
在将数组转换成最大堆之后,接着要进行交换数据,从而使数组成为一个真正的有序数组。
上面是当n=10时,交换数据的示意图。
当n=10时,首先交换a[0]和a[10],使得a[10]是a[0…10]之间的最大值;然后,调整a[0…9]使它称为最大堆。交换之后:a[10]是有序的!
当n=9时, 首先交换a[0]和a[9],使得a[9]是a[0…9]之间的最大值;然后,调整a[0…8]使它称为最大堆。交换之后:a[9…10]是有序的!
…
依此类推,直到a[0…10]是有序的。
堆排序实现
void maxHeapDown(vector<int> &vec, int start, int end) //堆的向下调整算法
{
if (vec.size() == 0) return;
int cur = start;
int lch = 2 * start + 1;
int tmp = vec[cur];
while (lch <= end)
{
if (lch < end && vec[lch] < vec[lch + 1])
{
lch++;
}
if (tmp >= vec[lch])
break;
vec[cur] = vec[lch];
cur = lch;
lch = 2 * cur + 1;
}
vec[cur] = tmp;
}
void maxHeapSort(vector<int> &vec) //堆排序
{
if (vec.size() == 0) return;
int size = vec.size();
for (int i = size / 2-1; i >= 0; i--) //初始化堆
{
maxHeapDown(vec, i, size - 1);
}
for (int i = size - 1; i > 0; i--)
{
swap(vec[0], vec[i]); //交换数据
maxHeapDown(vec, 0, i - 1); //剩余部分调整成最大堆
}
}
void maxHeapSortTest() //测试用例
{
vector<int> vec = { 7, 4, 4, 3, 1, 18, 6, 1, 5 };
maxHeapSort(vec);
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
}
时间复杂度和稳定性
时间复杂度
堆排序的时间复杂度是O(NlgN)。
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?
堆排序是采用的二叉堆进行排序的,二叉堆就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)。最多是多少呢?由于二叉堆是完全二叉树,因此,它的深度最多也不会超过lg(2N)。因此,遍历一趟的时间复杂度是O(N),而遍历次数介于lg(N+1)和lg(2N)之间;因此得出它的时间复杂度是O(NlgN)。
稳定性
堆排序是不稳定的算法。
原文链接如下:https://www.cnblogs.com/skywang12345/p/3602162.html#a42