简单易懂排序算法(五)【堆排序】

版权声明:本文为博主原创文章,转载请注明出处-- https://blog.csdn.net/qq_38790716/article/details/85960803

在分析堆排序之前首先对堆的概念进行一个简单的回顾,理解了堆的概念对于堆排序会有更好的理解

在这里插入图片描述
堆是具有下列性质的完全二叉树每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆(如上图图一所示); 或者每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆(如上图图二所示)

如果按照层序遍历的方式给节点从1开始编号,则节点之间满足以下关系:

k i k 2 i k_i \geq k_{2i}

k i k 2 i + 1 k_i \geq k_{2i + 1}

k i k 2 i k_i \leq k_{2i}

k i k 2 i + 1 k_i \leq k_{2i+1}

1 i n 2 1 \leq i \leq \frac{n }{2}

这个 i n 2 i \leq \frac {n}{2} 即对于一个n个节点的二叉树而言,如果 i i 等于1,则说明节点 i i 是二叉树的根,无双亲;如果 i i 大于1,则双亲是节点[ i 2 \frac {i}{2} ],因此i自然就是小于等于 n 2 \frac {n}{2}

堆排序

即利用堆进行排序的方法

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

利用该思想,得出如下代码:

void HeapSort(vector<int>& vec)
{
	int i;
	//这里vec.size() - 1是在数组前面加了一个0,方便下标进行操作
	//除以2是因为i 小于 n/2的节点**才有子节点**,这里用到了上述提到过的节点关系的思想
	for (i = (vec.size() - 1)/2;i > 0; --i)
	{
		HeapAdjust(vec, i, vec.size() - 1);  //将元素构造成大顶堆
	}
	for (i = vec.size() - 1;i > 0;--i)
	{
		swap(vec[i], vec[1]);  //交换堆顶元素与当前未经排序的子序列的最后一个记录
		HeapAdjust(vec, 1, i - 1); //将vec[1..i-1]重新调整为大顶堆
	}
}


void HeapAdjust(vector<int>& vec, int max_index, int length)
{
	int tmp, j;
	tmp = vec[max_index];  //记录下堆顶元素
	//以下沿元素值较大的孩子节点往下找
	for (j = 2 * max_index; j <= length; j *= 2) //这里j *= 2,之所以递增2倍也是节点关系讨论的结果
	{
		//找到当前节点的较大孩子节点的下标,即为j
		if (vec[j] < vec[j+1] && j < length)
			++j;
		//若当前父节点比其两个孩子节点都大,则不用调整
		if (tmp >= vec[j])
			break;
		//否则将将较大值与当前父节点交换,并记录下重新出发的点
		vec[max_index] = vec[j];
		max_index = j;  
	}
	vec[max_index] = tmp;
}

以上代码核心思想在于堆调整,无论构造大顶堆的过程亦或后续排序过程都离开堆调整这一步骤,而堆调整这一步骤又离不开大顶堆的思想,即将小节点往下移,大节点往上移

进行一个简单的测试
测试序列:0,50,10,90,30,70,40,80,60,20

在这里添加了一个0,因为我们堆的节点下标必然从1开始,之后才可以往下寻找子节点,如果下标从0开始则后续代码无意义

在这里插入图片描述

时间复杂度分析

由代码也可以大致看出,堆排序的运行时间主要是消耗在初始构建堆和在重建堆的反复筛选上

  • 在构建堆的过程中,因为我们是完全二叉树从最小层最右边的非终端节点开始构建,将它与其孩子进行比较和有必要的交换,对于每个非终端节点来说,其实最多进行两次比较和互换操作,因此构建堆的时间复杂度为O(n)
  • 在正式排序时,第i次取堆顶记录重建堆需要用O( log i \log{i} ) 的时间(完全二叉树的某个节点到根节点的距离为[ log 2 i \log_2 {i} ] + 1),并且需要取 n 1 n- 1 次堆顶记录,因此,重建堆的时间复杂度为O(n log n \log{n} )
  • 由于记录的比较与交换都是跳跃式进行,因此堆排序也是一种不稳定的排序方式
  • 由于初始构建堆所需比较次数较多,因此,不适合待排序序列个数较少的情况

测试代码

#include <iostream>
#include <vector>
using namespace std;

void HeapSort(vector<int>& vec);
void HeapAdjust(vector<int>& vec, int max_index, int length);
void PrintStep(vector<int> vec, int n, int i);
void PrintResult(vector<int> vec, int n);

void HeapSort(vector<int>& vec)
{
	int i;
	cout << "--------------构造大顶堆----------" << endl; 
	int count1 = 1;
	for (i = (vec.size() - 1)/2;i > 0; --i)
	{
		HeapAdjust(vec, i, vec.size() - 1);
		PrintStep(vec, vec.size() - 1, count1);
		++count1;
	}
	cout << "--------------堆排序--------------" << endl;
	int count2 = 1;
	for (i = vec.size() - 1;i > 0;--i)
	{
		swap(vec[i], vec[1]);
		HeapAdjust(vec, 1, i - 1);
		PrintStep(vec, vec.size() - 1, count2);
		++count2;
	}
	cout << "最终排序结果为:  ";
	PrintResult(vec, vec.size() - 1);
}


void HeapAdjust(vector<int>& vec, int max_index, int length)
{
	int tmp, j;
	tmp = vec[max_index];
	for (j = 2 * max_index; j <= length; j *= 2)
	{
		if (vec[j] < vec[j+1] && j < length)
			++j;
		if (tmp >= vec[j])
			break;
		vec[max_index] = vec[j];
		max_index = j;
	}
	vec[max_index] = tmp;
}
void PrintStep(vector<int> vec, int n, int i)
{
	cout << "第" << i << "次堆调整结果: ";
	for (int j = 1; j <= n; ++j)
		cout << vec[j] << ' ';
	cout << endl;
}

void PrintResult(vector<int> vec, int n)
{
	for (int j = 1; j <= n; ++j)
		cout << vec[j] << ' ';
	cout << endl;
}
int main(int argc, char **argv)
{
	int a[] = {0,50,10,90,30,70,40,80,60,20};
	vector<int> vec(a, a+10);
	HeapSort(vec);
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/qq_38790716/article/details/85960803
今日推荐