赞
踩
先将数组变成堆,然后将堆顶元素与数组尾元素交换,然后数组向左收缩,重新建堆……依此类推。
堆的向上调整法
void AdJustUp(HeapDateType* array, int child){
int parent = (child - 1) / 2;
while(child > 0){
if(array[child] < array[parent]){
Swap(&array[child], &array[parent]);
child = parent;
parent = (child - 1) / 2;
}
else{
break;
}
}
}
堆的向下调整法
void AdJustDown(HeapDateType* array, int arraySize, int parent){
int child = parent * 2 + 1;
while(child < arraySize){
if(child + 1 < arraySize && array[child] > array[child + 1]){
child++;
}
if(array[child] < array[parent]){
Swap(&array[child], &array[parent]);
parent = child;
child = parent * 2 + 1;
}
else{
break;
}
}
}
想要了解数据结构堆以及向上、向下调整法,请移步顺序二叉树(堆)与链式二叉树的C语言实现
需要用到数据结构——堆,(升序)建小堆,然后将数组元素全部插入进堆中,再依次取堆顶元素放入数组中。
但这种方式极其不好:
建堆的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(N∗logN),空间复杂度为 O ( N ) O(N) O(N)
排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(N∗logN),空间复杂度为 O ( 1 ) O(1) O(1)
若待排数据量庞大,则有可能不能一次性加载到内存中
void HeapSort(int* a, int n){
Heap obj;
HeapInit(&obj);
for(int i = 0; i < n; i++){
HeapPush(&obj, a[i]);
}
for(int i = 0; i < n; i++){
a[i] = HeapTop(&obj);
HeapPop(&obj);
}
HeapDestroy(&obj);
}
不利用数据结构——堆,而是直接对数组进行建堆,利用堆的向上调整法
建堆的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(N∗logN),空间复杂度为 O ( 1 ) O(1) O(1)
排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(N∗logN),空间复杂度为 O ( 1 ) O(1) O(1)
但也不推荐,因为有更快的方式三
void HeapSort(int* a, int n){
for(int i = 0; i < n; i++){
AdJustUp(a, i);
}
for(int i = n - 1; i > 0; i--){
Swap(&a[0], &a[i]);
AdJustDown(a, i, 0);
}
}
直接对数组进行建堆,利用堆的向下调整法
建堆的时间复杂度为 O ( N ) O(N) O(N),空间复杂度为 O ( 1 ) O(1) O(1),
排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(N∗logN),空间复杂度为 O ( 1 ) O(1) O(1)
void HeapSort(int* a, int n){
for(int i = (n - 1 - 1) / 2; i >= 0; i--){
AdJustDown(a, n, i);
}
for(int i = n - 1; i > 0; i--){
Swap(&a[0], &a[i]);
AdJustDown(a, i, 0);
}
}
堆排序一般只用方式三,这里仅对方式三进行分析
时间复杂度
建堆:
第一层, 2 0 2^{0} 20个节点,需要向下移动 h − 1 h-1 h−1层;
第二层, 2 1 2^{1} 21个节点,需要向下移动 h − 2 h-2 h−2层;
第三层, 2 2 2^{2} 22个节点,需要向下移动 h − 3 h-3 h−3层;
第四层, 2 3 2^{3} 23个节点,需要向下移动 h − 4 h-4 h−4层;
……
第 h − 1 h-1 h−1层, 2 h − 2 2^{h-2} 2h−2个节点,需要向下移动 1 1 1层;
则需要移动节点总的移动步数为:
T ( n ) = 2 0 ∗ ( h − 1 ) + 2 1 ∗ ( h − 2 ) + 2 2 ∗ ( h − 3 ) + . . . 2 h − 3 ∗ 2 + 2 h − 2 ∗ 1 T(n) = 2^{0}*(h-1)+2^{1}*(h-2)+2^{2}*(h-3)+...2^{h-3}*2+2^{h-2}*1 T(n)=20∗(h−1)+21∗(h−2)+22∗(h−3)+...2h−3∗2+2h−2∗1
计算得:
T ( n ) = n − l o g 2 n + 1 ≈ n T(n) = n - log_2^{n+1}\approx n T(n)=n−log2n+1≈n
因此建堆的时间复杂度为 O ( N ) O(N) O(N)
排序:
每次将堆顶元素放入数组尾时都会进行一次堆的向下调整,而由于堆的向下调整时间复杂度为 O ( l o g N ) O(log^{N}) O(logN),
则排序的时间复杂度为 O ( N ∗ l o g N ) O(N*log^{N}) O(N∗logN)
空间复杂度
仅仅使用了常数个辅助单元,空间复杂度是O(1);
稳定性
在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了;
因此堆排序是不稳定的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。