前言:
学数据结构学到堆了,学着学着发现居然可以用堆进行排序,挺有意思的。
思路:
既然叫做堆排序,首先应该了解什么是堆。堆其实就是完全二叉树(这个不懂自己搜搜去),不过要满足每个根节点都要比子节点大(或者小),这就是所谓的大根堆排序(小根堆排序)。
知道了堆的数据结构,怎么排序应该就不是什么困难的问题了吧?不!其实也是有一些问题在里面的。怎么会有问题呢?不就是把最上面的根的元素取出来继续维护这个堆再重复的取和维护的过程吗?有什么困难的。
有问题的,比如首先我们不希望牺牲额外的存储空间来建立一个真正的堆来维护数据,因此我们需要在原来的数组中进行所有的操作;
齐次,由于在数组中模拟堆,数组的0号位置是堆不能访问的,因此堆排序只能对下标从1开始的数据进行排序,想要覆盖正常数组中的所有数据,就必须对原数据进行迁移;
所以具体实现起来其实也没有那么简单。
首先是要建立和维护堆。我们维护堆是从下往上的进行维护,是先维护子树,再维护大一点的树。
我们需要知道的是,堆是树形结构,而且是一个完全二叉树,因此他的叶子节点一定是满足堆的定义的单个点,因此我们需要的就是找到第一个非叶子节点,这很容易,我们只需要找到堆的元素个数再用元素个数整除2就行了,维护完成以后再继续往前维护,直到头部。代码非常简单:
for(int i = length / 2 ; i >= 1;i--)
然后是核心部分,维护哪一种堆(大根堆或者小根堆)。
下面分别是维护大根堆和小根堆的小结构的代码(所谓小结构是指从a[i]到a[j],若除去a[i]就满足堆的一个结构)有点抽象,最好看看视频去。
void sift_MaxHeap(int a[], int head, int tail)//维护大根堆
{
int temp = a[head];
int p;
for (p = head * 2; p <= tail; p *= 2)
{
if (p + 1 <= tail && a[p] < a[p + 1])++p;//这步一定要注意 && 的前一个判断式,以防越界
if (temp >= a[p])break;
a[head] = a[p];
head = p;
}
a[head] = temp;
}
void sift_MinHeap(int a[], int head, int tail)//维护小根堆
{
int temp = a[head];
int p;
for (p = head * 2; p <= tail; p *= 2)
{
if (p + 1 <= tail && a[p] > a[p + 1])++p;
if (temp <= a[p])break;
a[head] = a[p];
head = p;
}
a[head] = temp;
}
上头维护两种堆的方法比较容易犯错的地方是里面的 if 表达式中的大于小于号。
所以综上,想要具体维护一个堆,需要这么写(以大根堆排序为例(大根堆是升序)):
for (int i = length / 2; i >= 1; i--)
sift_MinHeap(a, i, length);
我们已经可以维护一个堆,接下来需要的就是不断摘取根节点的元素,然后重新维护堆,循环往复至所有元素都被提出来。同时,由于维护堆的算法的局限性,我们只能将元素提到数组最后,而不能像我一开始想象的那样每次维护完堆以后,指针在数组中向后一个单位,直到结束。
下面分别是大根堆排序和小根堆排序的完整代码:
/* 大根堆排序 ,大根堆排序是升序 */
void sift_MaxHeap(int a[], int head, int tail)//维护大根堆
{
int temp = a[head];
int p;
for (p = head * 2; p <= tail; p *= 2)
{
if (p + 1 <= tail && a[p] < a[p + 1])++p;//这步一定要注意 && 的前一个判断式,以防越界
if (temp >= a[p])break;
a[head] = a[p];
head = p;
}
a[head] = temp;
}
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void max_heap_sort(int a[], int length)//大根堆排序(升序)
{
for (int i = length / 2; i >= 1; i--)
sift_MaxHeap(a, i, length);
for (int i = length; i >= 1; i--)//因为0号位置不用,所以i=length
{
swap(&a[1], &a[i]);
sift_MaxHeap(a, 1, i - 1);
}
}
/* 小根堆排序 ,小根堆排序是降序 */
void sift_MinHeap(int a[], int head, int tail)//维护小根堆
{
int temp = a[head];
int p;
for (p = head * 2; p <= tail; p *= 2)
{
if (p + 1 <= tail && a[p] > a[p + 1])++p;
if (temp <= a[p])break;
a[head] = a[p];
head = p;
}
a[head] = temp;
}
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void min_heap_sort(int a[], int length)//小根堆排序(降序)
{
for (int i = length / 2; i >= 1; i--)
sift_MinHeap(a, i, length);
for (int i = length; i >= 1; i--)
{
swap(&a[1], &a[i]);
sift_MinHeap(a, 1, i - 1);
}
}
测试用代码:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#define N 30
//之前写过了,就不再全写一遍了
void generate_random_number(int*, int, int);
void swap(int*, int*);
void sift_MinHeap(int a[], int head, int tail)//维护小根堆
{
int temp = a[head];
int p;
for (p = head * 2; p <= tail; p *= 2)
{
if (p + 1 <= tail && a[p] > a[p + 1])++p;
if (temp <= a[p])break;
a[head] = a[p];
head = p;
}
a[head] = temp;
}
void min_heap_sort(int a[], int length)//小根堆排序(降序)
{
for (int i = length / 2; i >= 1; i--)
sift_MinHeap(a, i, length);
for (int i = length; i >= 1; i--)
{
swap(&a[1], &a[i]);
sift_MinHeap(a, 1, i - 1);
}
}
int main()//10
{
int arr[N + 10] = { 0 };
generate_random_number(arr, 0, 1024);
min_heap_sort(arr,N - 1);
printf("排序后数列:\n");
for (int i = 0; i < N; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
测试结果:
由于堆排序只能从下标1开始,所以排序结果下标为0的元素不用看
至此,堆排序结束。