您的位置:首页 > 其它

关于堆排序(参考自算法导论)

2012-05-30 13:51 295 查看
堆排序是一种很有用的排序算法,有用的并不是在排序上的用处,而是那个大根堆和小根堆的建立,在平时的运用中,举足轻重!一个最有用的实例就是操作系统的进程的最大优先权调度算法。从很多进程中,找到优先级最大的进程,然后分配CPU资源。堆排序的主要步骤也就是创建堆。一旦最大堆(最小堆)创建好了,排序也是十分简单的事情了。下面的我们全部以大根堆来做讲解

堆是一种数据结构,可以理解成为一种完全二叉树,但是有个苛刻的要求:除了根节点外的所有节点的父节点的值要大于该节点的值。其实就是所有父节点的值要大于子节点的值。比如下图描述的结构:



我们有了这种结构之后,就可以确定从堆顶所得到的值一定是最大值,我们可以把最大值换出来,然后再次把我们的结构打乱了的堆重新组合成为标准的大根堆,这就是整个堆排序的过程。堆排序的主要时间也是花在组件堆和重建堆上。

组建堆的过程:

说到最重要的一个子环节:保持最大堆 max_heapify

说明:这个操作可以使堆中的某个节点在其子树满足堆性质的前提下,让它及其子树满足堆的性质。



下图的堆中,发现第二个节点不满足堆的性质,且它的子节点对应的子树都是满足堆的性质的,所以进行如下操作,让其重新调整为大根堆

max_heapify:

1)对比节点与其两个子节点,选出值最大的一个节点,如果当前节点不是最大,就把子节点中最大的一个和当前节点进行交换操作

2)由于第一个操作可能让交换节点的那棵子树失去堆的性质,所以对交换后的节点也进行第一布操作,这是一个递归的过程

以max_heapify(pos=2)为例的过程如图:

(1、交换节点)



(2、对交换节点进行递归调用)



从上面的操作可以看出,如果我们的子树都是符合大根堆的,则可以依从下往上的的让整个树都变成大根堆。由此特性,我们可以写出让整个树变成大根堆堆的算法:

build_max_heap

ALL)对N/2到1号节点进行max_heapify操作,让整个树变成大根堆。(因为在完全二叉树中只有N/2的节点存在子树其余N/2是叶子)过程我就不再画图了。

以上两步就是组成大根堆的所有过程了,简单吧。接下来就是排序过程了

heap_sort

1)把最后节点和首节点进行交换,得到最大值,放到末尾。

2)从堆中去掉最后一个节点,我们可以用heap_size来表示,heap_size减1即可。对从第一个节点进行max_heapify操作,取得第二大的值。

3)重复1和2操作,依次得到从大到小的所有的值。直到堆只有1个元素为止

具体过程如下:(只针对heap_size为10的情况)

(1、把最后节点和首节点交换,堆大小减1)



(2、堆首节点进行max_heapify操作,重建堆)



下面我们简单的对堆排序进行时间复杂度分析:

max_heapify的时间复杂度为O(lgN)(因为完全二叉树树的高度为lgN,最多只会进行二叉树高度次变换)

build_max_heap操作会N/2次调用max_heapify,则时间复杂度为O(N/2*lgN)

heap_sort操作会N次调用max_heap_fy,则时间复杂度为O(N*lgN)

综合上面,时间复杂度为O(N*lgN)+O(N*lgN)=O(N*lgN)

堆排序的效率还是很高的。至少是比较类的排序算法中最优的。

另外,很容易看出堆排序不是稳定的排序算法,因为如果2个相同的值,不是在一棵子树里面,而是分开的,很可能就把后面的那个值交换到了前面。这不用多说了。

总结:堆排序的主要用处并不是光在于它的排序,而是在于堆的这个性质,最大值或者最小值永远在第一个,而且保持性质的所付出的时间代价很小。这样可以做出很多有用的东西,比如优先级队列就是很好的一个应用。下面附上我自己实现的堆排序的C++源代码。

堆排序源码地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: