七大排序算法之改进算法系列(五)——堆排序

堆排序(Heap Sort):就是利用堆(假设利用大顶堆)进行排序的方法。

堆排序可以看成是简单选择排序的一种升级版本。

为了弄清楚堆排序,首先来学习几个概念,分别是大顶堆小顶堆

是具有下列性质的完全二叉树:每个节点的值都大于或者等于其左右孩子节点的值,称为大顶堆;或者每个节点的值都小于或者等于其左右孩子节点的值,称为小顶堆

七大排序算法之改进算法系列(五)——堆排序_第1张图片

可见,根结点一定是堆中所有结点最大或者最小者,如果按照层序遍历的方式给结点从1开始编号,则结点之间满足如下关系:

 ki>=k2i 且 ki>=k2i+1或者 ki<=k2i 且 ki<=k2i+1 其中 (1<=i<=⌊n/2⌋)

下标i与2i和2i+1是双亲和子女关系。

基本思想:将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的 n-1 个序列重新构造成一个堆,这样就会得到 n 个元素中的次大值。如此反复执行,便能得到一个有序序列了。

有了上面的基本思想的指导,接下来要解决的第一件事情就是,如何使用已经给出的数据构造一个大顶堆?

当然有办法,观察一下完全二叉树可以看到,只要把每个拥有孩子的结点形成的子树排成大顶堆,那么整个树就可以构成大顶堆,我们按照自下而上自右向左的顺序选择结点,分别构造大顶堆即可。大概的构造过程如下图所示(图非本人绘制):

七大排序算法之改进算法系列(五)——堆排序_第2张图片

堆排序代码如下:

#include 
int Mycount = 0;
using namespace std;


void swap(int k[], int i, int j)
{
	int temp;

	temp = k[i];
	k[i] = k[j];
	k[j] = temp;
}

void HeapAdjust(int k[], int s, int n)
{
	int i, temp;

	temp = k[s];

	for (i = 2 * s; i <= n; i *= 2)
	{
		Mycount++;
		if (i < n && k[i] < k[i + 1])
		{
			i++;
		}

		if (temp >= k[i])
		{
			break;
		}

		k[s] = k[i];
		s = i;
	}

	k[s] = temp;
	
}

void HeapSort(int k[], int n)
{
	int i;

	for (i = n / 2; i > 0; i--)
	{
		HeapAdjust(k, i, n);
	}

	for (i = n; i > 1; i--)
	{
		swap(k, 1, i);
		HeapAdjust(k, 1, i - 1);
	}
}

int main()
{
	int i;
	int a[10] = { -1, 5, 2, 6, 0, 3, 9, 1, 7, 4 };
	//int a[10] = { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9 };
	//int a[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };

	cout << "排序前的数组是:";
	for (i = 0; i < 10; i++)
	{
		cout << a[i];
	}
	cout << endl;

	HeapSort(a, 9);

	cout << "总共执行" << Mycount << "次比较!" << endl;
	//printf("总共执行 %d 次比较!", count);
	//printf("排序后的结果是:");
	cout << "排序后的结果是:";
	for (i = 0; i < 10; i++)
	{
		cout << a[i];
	}
	cout << endl;

	return 0;
}

执行结果:


堆排序算法的复杂度分析:

在构建堆的过程中,只需要将结点与孩子进行比较,若有必要则进行互换,因此整个构建堆的时间复杂度为O(n)

在正式排序时,因为完全二叉树的深度为,所以重建堆的时间复杂度为O(nlogn)

所以堆排序的时间复杂度为O(nlogn)


堆排序算法的稳定性:

由于堆排序的比较和交换都是跳跃式的,因此堆排序是一种不稳定的排序算法。

你可能感兴趣的:(数据结构与算法)