堆排序是由1991年的计算机先驱奖获得者、斯坦福大学计算机科学系教授罗伯特.弗洛伊德(Robert W.Floyd)和威廉姆斯(J.Williams)在1964年共同发明了的一种排序算法( Heap Sort );
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。
堆
基本介绍
先直观感受一下,下面就是一个堆:
20 17 8 7 16 3
什么??上面不就一个数组吗……?!
没错,(二叉)堆数据结构是一种数组对象。
不过,让我们用另外一种方式来看这个数组:
对于表示堆的数组arr[0…n-1],我们以arr[0]为根,给定某个节点下标i,令其父节点和左右后代节点的下标为:
parent(i) = (i-1)/2;
left(i) = 2*i+1;
right(i) = 2*i+2;
(具体实现时,可用移位来实现乘以2和除以2)
于是,它可以看作一棵完全二叉树:
可是,这也只是一棵完全二叉树,有啥特别之处呢?
特点就是:除根节点以外的每个节点i,都有arr[ parent(i) ] >= arr[i]。
堆分为最大堆和最小堆,上面就是最大堆,最小堆的特点则是:除根节点以外的每个节点i,都有arr[ parent(i) ] <= arr[i]。
堆排序一般使用最大堆,最大堆中的最大元素位于根节点。
因为具有n个元素的堆是基于一颗完全二叉树的,所以其高度为O(log n)。
算法分析
其实这种算法看起来挺复杂,但是如果真正理解了就会感觉非常简单的;
基本思想:把待排序的元素按照大小在二叉树位置上排列,排序好的元素要满足:父节点的元素要大于等于其子节点;这个过程叫做堆化过程,如果根节点存放的是最大的数,则叫做大根堆;如果是最小的数,自然就叫做小根堆了。根据这个特性(大根堆根最大,小根堆根最小),就可以把根节点拿出来,然后再堆化下,再把根节点拿出来,,,,循环到最后一个节点,就排序好了。
基本步骤:
其实整个排序主要核心就是堆化过程,堆化过程一般是用父节点和他的孩子节点进行比较,取最大的孩子节点和其进行交换;但是要注意这应该是个逆序的,先排序好子树的顺序,然后再一步步往上,到排序根节点上。然后又相反(因为根节点也可能是很小的)的,从根节点往子树上排序。最后才能把所有元素排序好;
保持堆的性质
在清楚什么是最大堆之后,我们来谈一谈如何保持堆的性质,也就是说,如果堆中有节点不满足堆的性质,我们如何进行调整。
首先,我们假定以节点i的左右儿子为根的两棵二叉树都是最大堆,而以节点i为根的二叉树可能不是最大堆,则调整的过程如下:
- 从元素arr[i], arr[left(i)], arr[right(i)]中找出最大的元素,将下标存在largest中;
- 如果arr[i]是最大的,说明以节点i为根的二叉树是最大堆,无须调整,程序结束;否则,交换arr[i]和arr[largest],于是arr[i], arr[left(i)], arr[right(i)]三者满足了最大堆的性质,但是交换后,下标为largest的节点存放arr[i]的值,以该节点为根的子树又可能违反最大堆的性质,因此需要对该子树递归调用本调整过程。
时间复杂度