C语言实现----堆排序

42 篇文章 2 订阅
10 篇文章 3 订阅

一、堆排序介绍

堆排序( Heap Sort )是指利用堆这种数据结构所设计的一种排序算法。

因此,学习堆排序之前,有必要了解堆!若读者不熟悉堆,建议先了解堆,然后再来学习本章。

我们知道,堆分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。

鉴于最大堆和最小堆是对称关系,理解其中一种即可。本文将对最大堆实现的升序排序进行详细说明。

看本文之前可以看看--->>B站视频讲解

最大堆进行升序排序的基本思想:

①初始化堆:将数列 a [1... n ]构造成最大堆。

②交换数据:将 a [1]和 a [n]交换,使 a [n]是 a [1.. n]中的最大值;然后将 a [1.. n -1]重新调整为最大堆。接着,将 a [1]和 a [n -1]交换,使 a[ n -1]是 a[1..n-1]中的最大值;然后将 a [1..n -2]重新调整为最大堆。依次类推,直到整个数列都是有序的。

下面,通过图文来解析堆排序的实现过程。注意实现中用到了"数组实现的二叉堆的性质"。

在第一个元素的索引为0的情形中:

性质一:索引为的左孩子的索引是(2* i +1);

性质二:索引为的右孩子的索引是(2* i +2);

性质三:索引为的父结点的索引是 floor((i-1)/2);     floor()为向下取整。

 例如,对于最大堆{110,100,90,40,80,20,60,10,30,50,70} 而言:索引为0的左孩子的索引是1;索引为0的右孩子的索引是2;索引为8的父节点是3.

二、堆排序图文说明

1. 堆排序(升序)代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

// (最大)堆的向下调整算法
// 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
// 其中,N为数组下标索引值,如数组中第1个数对应的N为0。
// 
// 参数说明
// a -- 待排序的数组
// start -- 被下调节点的起始位置(一般为0,表示从第一个开始)
// end -- 截止范围(一般为数组中最后一个元素的索引)

void maxheap_sort(int a[], int start,int end) {
	int c = start; // 当前(current)节点的位置
	int l = 2 * c + 1; // 左(left)孩子的位置
	int tmp = a[c]; // 当前(current)节点的大小
	for (; l <= end; c = l, l = 2 * l + 1) {
		// “l”是左孩子,“l+1”是右孩子
		if (l < end && a[l] < a[l + 1]) {
			l++; // 左右两个孩子中选择较大者,即m_heap[l+1]
		}
		if (tmp >= a[l]) {
			break; // 调整结束
		}
		else {// 交换值
			a[c] = a[l];
			a[l] = tmp;
		}
	}
}

// 堆排序(从小到大)

// 参数说明:
// a -- 待排序的数组
// n -- 数组的长度

void heap_sort_asc(int a[], int n) {
	int i;
	// 从(n/2-1)--> 0 逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆
	for (i = n / 2 - 1; i >= 0; i--) {
		maxheap_sort(a, i, n - 1);
	}
	// 从最后一个元素开始对序列进行调整,不断地缩小调整的范围直到第一个元素
	for (i = n - 1; i > 0; i--) {
		// 交换a[0]和a[i]。交换后,a[i]是a[0..i]中最大的。
		swap(&a[0], &a[i]);
		// 调整a[0..i-1],使得a[0..i-1]仍然是一个最大堆。
		// 即,保证a[i-1]是a[0..i-1]中的最大值。
		maxheap_sort(a, 0, i - 1);
	}
}



int main() {
	int arr[] = { 9,5,1,6,2,3,0,4,8,7 };
	heap_sort_asc(arr, 10);
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr[i]);
	}
	printf("\n");

	return 0;
}

heap_sort_asc(a,n)的作用是:对数组a进行升序排序;其中,a是数组,n是数组长度。

heap_sort_asc(a,n) 的操作分为两部分:初始化堆和交换数据。

maxheap_down(a, start, end)是最大堆向下调整算法。

下面演示heap_sort_asc(a,n) 对 a={20,30,90,40,70,110,60,10,100,50,80}, n = 11 进行堆排序过程。下面是数据 a 对应的初始化结构:

 2. 初始化堆

在堆排序算法中,首先要将待排序的数组转化为二叉堆。下面演示将数组 {20,30,90,40,70,110,60,10,100,50,80}转换为最大堆 {110,100,90,40,80,20,60,10,30,50,70}的步骤。

i = 11/2 - 1,即i = 4

 上面是maxheep_down(a,4,9)调整过程。

maxheap_down(a,4,9)的作用是将a[4..9]进行下调;a[4] 的左孩子是a[9],右孩子是 a[10]。调整时,选择左右孩子中较大的一个(即a[10])和a[4]  交换。

i = 3

 上面是maxheap_down(a,3,9)调整过程。

maxheap_down(a,3,9)的作用是将 a[3..9]进行下调; a[3]的左孩子是 a[7], 右孩子是a[8]。调整时,选择左右孩子中较大的一个(即a[8])和a[4] 交换。

i = 2

 上面是maxheap_down(a,2,9)调整过程。

maxheap_down(a,2,9)的作用是将 a[2..9]进行下调; a[2]的左孩子是 a[5], 右孩子是a[6]。调整时,选择左右孩子中较大的一个(即a[5])和a[2] 交换。

i = 1

 上面是maxheap_down(a,1,9)调整过程。

maxheap_down(a,1,9)的作用是将 a[1..9]进行下调; a[1]的左孩子是 a[3], 右孩子是a[4]。调整时,选择左右孩子中较大的一个(即a[3])和a[1] 交换。交换之后,a[3] 为 30,它比它的右孩子a[8] 要大,接着,再将他们交换。

i = 0

上面是 maxheap _ down ( a ,0,9)调整过程。

maxheap _ down ( a ,0,9)的作用是将 a [0..9]进行下调; a [0] 的左孩子是 a [1],右孩子是 a [2]。调整时,选择左右孩子中较大的一个(即 a [2])和 a[0] 交换。交换之后, a [2]为20,它比它的左右孩子要大,选择较大的孩子(即左孩子)和 a [2]交换。

调整完毕,就得到了最大堆。此时,数组{20,30,90,40,70,110,60,10,100,50,80}也就变成了{110,100,90,40,80,20,60,10,30,50,70}。

3. 交换数据

在将数组转换成最大堆之后,接着要进行交换数据,从而使数组成为一个真正的有序数组。

交换数据部分相对比较简单,下面仅仅给出将最大值放在数组末尾的示意图。

上面是当 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]是有序的。

三、堆排序的时间复杂度和稳定性

1. 堆排序的时间复杂度

堆排序的时间复杂度是 O ( N * IgN )。

假设被排序的数列中有 N 个数。遍历一趟的时间复杂度是 O ( N ),需要遍历多少次呢?堆排序是采用的二叉堆进行排序的,二叉堆就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是 Ig ( N +1)。最多是多少呢?由于二叉堆是完全二叉树,因此,它的深度最多也不会超过 Ig (2N)。因此,遍历一趟的时间复杂度是 O ( N ),而遍历次数介于 Ig ( N +1)和 lg (2N)之间;因此得出它的时间复杂度是O ( N * IgN )。

2. 堆排序的稳定性

堆排序是不稳定的算法,它不满足稳定算法的定义。它在交换数据的时候,是比较父结点和子节点之间的数据所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化。

算法稳定性:假设在数列中存在 a[i]= a[j] ,若在排序之前, a [i]在 a[j] 前面并且排序之后, a [i]仍然在 a [ j ]前面。则这个排序算法是稳定的!

  • 8
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值