排序算法-8-堆排序

堆排序

堆排序是利用堆(Heap)来进行排序的一种方法,在堆排序中,我们需要引入一种叫做“堆”的数据结构。

堆一般可以看做是一棵完全二叉树,且堆中某个节点的值总是不大于或不小于其父节点的值,前者我们称之为最大堆,后者称之为最小堆。

原理

堆排序的思想是先用所有元素建立一个最大堆(当然,这里用最小堆也是可以的,方法类似最大堆,不失一般性,就只讲最大堆了),然后每次取出最大的那个元素,并把剩下的元素再次调整为一个最大堆,如此循环。步骤如下:

  1. 用所有元素建立一个最大堆。
  2. 取出最大的元素,将其与堆中的最后一个元素交换。
  3. 因为已经取出了一个元素,将堆的大小减一。
  4. 调整剩下的堆中元素重新组成一个最大堆。
  5. 重复2-4步,直到堆中只剩最后一个元素为止。

建堆和调整堆为最大堆都需要用到堆调整的功能,其前提是调整前只有顶点元素不满足最大堆的条件。此功能步骤如下:

  1. 记录顶点元素的值。
  2. 找到顶点、其左孩子、其右孩子三者中的最大一个,若最大者为顶点,结束;否则交换最大者与顶点。
  3. 对以最大者为根的子堆重复1,2步,直到结束。
    注意这里第3步的实现可以用递归,也可以用循环。

实现

按照以上原理我们来用代码实现。

下面就是用C语言实现的代码。分成两个函数来实现。

  • 要排序的数组a有n个元素。
  • heap_sort 先建最大堆,然后每次取出最大的那个元素a[0]放入已排序区,再调用 heap_adjust 调整剩下的堆为最大堆。
  • heap_adjust 的输入中p、r分别指向堆的第一个元素和最后一个元素。每次找到父亲节点、左孩子、右孩子中最大的那个,将其调整为父亲节点,然后如有需要,继续调整子堆。这里没有用递归,用了循环。
/* p、r分别指向堆的第一个元素和最后一个元素 */
void heap_adjust(int a[], int p, int r)
{
	int tmp = a[p];                   //p指向要调整加入的堆顶点,tmp是其值

	for (int i=2*p+1; i<=r; i=2*p+1) {//a[i]为a[p]的左孩子,a[i+1]为a[p]的右孩子
		if (i<r && a[i]<a[i+1]) i++;  //找出两个孩子中大的一个,用i指向他
		if (tmp > a[i]) break;        //若父亲比最小的孩子大,则已调整好
		a[p] = a[i];                  //否则将最大的孩子调整为父亲,
		p = i;                        //并将p指向要调整的子堆顶点
	}
	a[p] = tmp;                       //将要加入的元素放入调整好的位置
}

void heap_sort(int a[], int n)
{
	int i;

	/* 建最大堆 */
	for (i=n/2-1; i>=0; i--)
		heap_adjust(a, i, n-1);

	/* 每次取出当前最大元素a[0],然后调整堆为最大堆 */
	for (i=n-1; i>0; i--) {
		swap(&a[0], &a[i]);     // 将a[0]放到已排序区域
		heap_adjust(a, 0, i-1); // 调整剩下的堆为最大堆
	}
}

void swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

为了验证此函数的效果,加上了如下辅助代码,对3个数组进行排序,运行结果在最后,可见排序成功。

#include <stdio.h>
#include <stdlib.h>

#define SIZE_ARRAY_1 5
#define SIZE_ARRAY_2 6
#define SIZE_ARRAY_3 20

void heap_sort(int a[], int n);
void show_array(int a[], int n);

void main()
{
	int array1[SIZE_ARRAY_1]={1,4,2,-9,0};
	int array2[SIZE_ARRAY_2]={10,5,2,1,9,2};
	int array3[SIZE_ARRAY_3];

	for(int i=0; i<SIZE_ARRAY_3; i++) {
		array3[i] = (int)((40.0*rand())/(RAND_MAX+1.0)-20);
	}

	printf("Before sort, ");
	show_array(array1, SIZE_ARRAY_1);
	heap_sort(array1, SIZE_ARRAY_1);
	printf("After sort, ");
	show_array(array1, SIZE_ARRAY_1);

	printf("Before sort, ");
	show_array(array2, SIZE_ARRAY_2);
	heap_sort(array2, SIZE_ARRAY_2);
	printf("After sort, ");
	show_array(array2, SIZE_ARRAY_2);

	printf("Before sort, ");
	show_array(array3, SIZE_ARRAY_3);
	heap_sort(array3, SIZE_ARRAY_3);
	printf("After sort, ");
	show_array(array3, SIZE_ARRAY_3);
}

void show_array(int a[], int n)
{
	if(n>0)
		printf("This array has %d items: ", n);
	else
		printf("Error: array size should bigger than zero.\n");

	for(int i=0; i<n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}

运行结果:

Before sort, This array has 5 items: 1 4 2 -9 0
After sort, This array has 5 items: -9 0 1 2 4
Before sort, This array has 6 items: 10 5 2 1 9 2
After sort, This array has 6 items: 1 2 2 5 9 10
Before sort, This array has 20 items: 13 -4 11 11 16 -12 -6 10 -8 2 0 5 -5 0 18 16 5 8 -14 4
After sort, This array has 20 items: -14 -12 -8 -6 -5 -4 0 0 2 4 5 5 8 10 11 11 13 16 16 18

分析

时间复杂度

从代码可见,heap_adjust 的过程是把二叉树的顶点元素往子树移动,最多移动到叶节点,即最多有 logn\log n 次,所以 heap_adjust 的时间复杂度为 O(logn)O(\log n)

而 heap_sort 中的 for 循环会遍历一次整个数组,量级为 nn,for 循环中调用 heap_adjust,所以整个堆排序的时间复杂度为 O(nlogn)O(n\log n)

空间复杂度

因为堆排序可以原址进行,所以其空间复杂度为 O(1)O(1)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章