在堆的代码上基础上写本篇文章,堆的代码见:树,二叉树与堆
之前写的堆并不能说堆排序,只是堆的实现,之所以这样说是因为之前有建堆的过程 ,新开辟了一份空间,之前的数据并没有改变。
在原数据上进行建成堆
向上调整建堆
如何只在原来数据上进行堆排序:
直接模拟堆插入的过程,然后向上调整。之前插入新的数据才向上调整,但是函数的参数只是数组的下表,有了数组下标就可以调整,所以直接调整数组的数据也没有问题。
时间复杂度:O(n*logn)
void HeapSort(HPDatetype* p, int n)
{
assert(p);
for (int i = 1; i < n; i++)//i = 1,从第二个数据开始向上调整
{
AdjustUP(p, i);
}
}
主函数:
void test3()
{
int arr[] = { 4, 6, 2, 1, 5, 8, 2, 9 };
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
HeapSort(arr, sizeof(arr) / sizeof(arr[1]));
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
test3();
return 0;
}
向下调整建堆
向上调整建堆:从第二个子结点开始一直和上面的比较,每个节点都要比较至根结点。
向下调整建堆:从倒数第一个非叶子节点(最后一个叶子结点的父结点)开始向下比较,每一个数都要比较到最后一个。
时间复杂度比较:
向上调整建堆:向上调整建堆和之前开辟空间建堆的时间复杂度是一样的,二着本质都是插入建堆,只是一个开辟了空间,一个没有开辟。具体可见堆中最后的时间复杂度分析
向下调整建堆:
因此:建堆的时间复杂度为O(N)。
对比可以发现,向上调整是越往后每一层数据越多,向上调整的高度也变大,而向下调整是越往上每一层数据越来越少,调整的高度变多。明显向下调整时间复杂度更低。
总结就是向上的是结点多的调整次数多节点少的调整次数少
向下的是节点多的调整次数少,结点少的调整次数多。
同样对于一个无序的数组,只需要向下调整即可建立堆
void HeapSort(HPDatetype* p, int n)//不开辟空间,在原数组上进行堆排序
{
assert(p);
/*for (int i = 1; i < n; i++)
{
AdjustUP(p, i);
}*/
for (int i = (n-1 - 1) / 2; i >= 0; --i)//n - 1是最后一个结点的下标,再减一除2就是最后一个非叶子结点
{
AdjustDown(p, n, i);
}
}
对数据进行升序排列
对一组数据进行降序排列,如果使用传统的遍历,那么时间复杂度很高比如冒泡排序为O(n^2),这是可以利用堆的性质进行排序,时间复杂度为O(n*logn)。
建立大堆,此时逻辑上是有序的大堆,但是物理结构上不是,而且也不是升序排列的数据。之所以建立大堆,是因为每次堆顶都是最大的数,选择最大的数以后,根节点的子树还是大堆,调整数据时就会很方便,如果建立小堆,那么只能选出最小的那个数,再向下选择会越往下越麻烦,数据量会越来越大。选择前两个小数好选,在第三个数往后就不好选了 ,比较数的大小时,要么不停的建立堆,进行查找最小的数,要么直接比较,但是在高度为3往后在比较就很难比较了,而且越往后,执行的次数越多,要不停的遍历,效率很低。
如果选择一个最小的数就建立一次堆,时间复杂度就会很高。所以建立一个大堆,选择最大的数,然后选择次大的数,不断的沉到最后面。这样就是一个升序的数组了。之前想过可以建立小堆进行排序,也是首尾交换,但是放在数组里面就算是降序了,所以这种方式适合降序排列数组的时候使用。
实现逻辑:
先建立一个大堆,此时最大的数在上面,而且堆的逻辑结构清晰,但是无法找到升序的排列。
所以将最大的数和最后一个数交换,此时最大的数在队尾,但是只有根节点的子树是大堆,所以让堆顶的数向下调整。使整个堆变为大堆。此时次大的数在堆顶
然后队尾舍弃掉刚刚最大的数,队尾变为刚刚数的前一个。只是调整时逻辑位置舍弃了,实际上还存在。
如此循环,让最大的数不停的去到队尾,最后就得到了升序排列的数
降序只需要将大堆换成小堆就行,逻辑一样。
以降序代码为例:
void HeapSort(HPDatetype* p, int n)//不开辟空间,在原数组上进行堆排序
{
assert(p);
for (int i = 1; i < n; i++)
{
AdjustUP(p, i);
}
int end = n - 1;//end是数组下标,n是数据个数
while (end > 0)
{
Swap(&p[0], &p[end]);
AdjustDown(p, end, 0);//第二个参数是数据个数,少了队尾数据个数就是队尾的数据下标
--end;
}
}
主函数:
#include "Heap.h"
void test3()//堆排序
{
int arr[] = { 4, 6, 2, 1, 5, 8, 2, 9 };
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
HeapSort(arr, sizeof(arr) / sizeof(arr[1]));
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
test3();
return 0;
}
如此就实现了数据的降序排列