排序算法理解——堆排序

今天来讲解堆排序的理解和实现。

1、先明确一下定义。

是一种数据结构,一种叫做完全二叉树的数据结构。

2、堆的性质

这里我们用到两种堆,其实也算是一种。

大顶堆:每个节点的值都大于或者等于它的左右子节点的值。

小顶堆:每个节点的值都小于或者等于它的左右子节点的值。

既然是完全二叉树,也就意味着我们可以根据一个节点的索引(i),计算出其左右孩子节点。
左子节点 = 2i+1 右子节点 = 2i+2。

分析思路

关键在于我们要怎么利用堆的性质来帮助我们进行排序?老规矩,仍然从思想解释的根本说起。

堆排序的基本思想是:1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。

提到了三点,接下来用设定函数来划分他们。
1.为序列建造(初始化)一个大顶堆,堆顶元素即为序列最大元素。
2.将1中的最大元素挑出来,序列规模减小1。很自然地,我们想通过这种模式来依次得到当前堆中的最大元素,最后组成有序序列。因此将堆顶元素和最后元素交换,使得从结尾开始排序,而交换后剩下的节点不能保证还是大顶堆,因此要重新构建,以方便下次取下堆顶元素与结尾交换。
3.不断重复步骤2,取堆顶-》重构剩余-》取堆顶-》重构剩余…直至获取至最后元素,我们便可以获得有序序列。需要强调的是,这里我们说的“”,实际是让其与最后一个元素交换,重构也是为了保持堆的性质。这样我们不用再开辟额外空间操作。自然地利用完全二叉树形成数组的这个特点。函数命名为HeapSort()

细节实现

通过分析不难发现,其中最关键的就在于怎么样实现给你一个序列,将其实现为符合大顶堆的数据结构。根据一开始所给出的定义,关键就是要保证当前顶点和其每个左右子节点的大小关系,通过遍历保证他们都符合定义,若是不符合就需要交换到合适的顺序上。
选择从上向下还是从下至上呢?当我们从上到下比较的时,如果当前节点的子节点也存在子节点,也就是还没遍历到后面节点的时候,后面的节点可能不满足大顶堆的性质。存在调整的可能性,从上到下有可能还需要回来调整当前节点,即存在父节点的二次调整。而使用从下到上的方式避免了这种情况的出现。
我们用到的基本元素,当前节点i,左孩子节点2i+1,右节点2i+2;因此可以把框架写出

void heapify(int arr[],int i,int len){
int maxIndex = i;
int left = 2*i+1;
int right = 2*i + 2;
if(左子节点>当前最大节点){
    更新当前最大节点为左子节点
  }
if(右子节点>当前最大节点){
    更新当前最大节点为右子节点
}
if(最大节点改变了){
   swap(arr[maxIndex],arr[i]);  //更换位置
   heapify(maxIndex,len);    // 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
   }
}

核心套件heapify()写完了,他表示从当前节点i开始,向上构建的大顶堆过程。接下来就是调用它,已完成我们之前提到的功能。

  1. 初始化大顶堆
    既然要与左右孩子节点比较,因此我们只需要找到最后一个非叶子节点,从后往前调用heapify即可。
    根据完全二叉树的性质,最后一个非叶子节点为arr.length/2-1
void buildBigHeap(int arr[],int len){ //对非叶子节点构造大顶堆 
	for(int i = floor(len/2)-1;i>=0;i--){
		heapify(arr,i,len);
	}
}

2.从结尾开始,替换堆顶元素,然后对剩下的节点重构成大顶堆。

void heapSort(int arr[],int len){
    buildBigHeap(arr,len);  //初始化构建最大堆
	 
	//把尾部元素和堆顶元素互换
	for(int i = len-1;i>0;i--){
		swap(arr,0,i);  //首尾交换 
		len--;  //需要重构的长度减少
		heapify(arr,0,len); 
	}
	
} 

完整代码

#include<iostream>
#include<cstdio> 
#include<math.h>
using namespace std;

void swap(int arr[],int a, int b){
	int tmp = arr[a];
	arr[a] = arr[b];
	arr[b] = tmp;
}

void heapify(int arr[],int i,int len){
    int maxIndex = i; //把当前节点当作最大值
	int left = 2*i+1; //利用完全二叉树的性质 
	int right = 2*i+2;
	if(left<len && arr[left]>arr[maxIndex]){  
	//注意这里应该是和maxIndex比较,要保证比较的对象始终是当前圈子里的最大值,如果写出i的话,举个例子 左(3) 当前(1)右(2),最后maxIndex为2,是错的。
		maxIndex = left;
	} 
	if( right<len && arr[right]>arr[maxIndex]){
		maxIndex = right;
	}
	if(i!=maxIndex){
		swap(arr,i,maxIndex);
		heapify(arr,maxIndex,len); //考虑被影响的子结构 
	}
}

void buildBigHeap(int arr[],int len){ //对非叶子节点构造大顶堆 
	for(int i = floor(len/2)-1;i>=0;i--){
		heapify(arr,i,len);
	}
}

void heapSort(int arr[],int len){
    buildBigHeap(arr,len);  //初始化构建最大堆
	 
	//把尾部元素和堆顶元素互换
	for(int i = len-1;i>0;i--){
		swap(arr,0,i);  //首尾交换 
		len--;  //需要重构的长度减少
		heapify(arr,0,len); 
	}
	
} 


int main(){
	int arr[10] = {8,4,9,5,3,2,7,6,1,0};
	heapSort(arr,10); 
	for(int i =0;i<10;i++)
	cout<<arr[i]<" ";
}

写在结尾

因为堆排序无关乎初始序列是否已经排序已经排序的状态,始终有两部分过程,构建初始的大顶堆的过程时间复杂度为O(n),交换及重建大顶堆的过程中,需要交换n-1次,重建大顶堆的过程根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以它最好和最坏的情况时间复杂度都是O(nlogn),空间复杂度O(1)。

参考来源:
https://blog.csdn.net/qq_28063811/article/details/93034625

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值