堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,可以利用数组的特点快速定位指定索引的元素。
要理解排序算法首先要知道堆的定义:堆 即一颗完全二叉树,树中任一个非叶子节点的关键字均不大于(或者均不小于)其左右孩子(若存在)节点的关键字。
比如说想下面这样的完全二叉树就叫最小堆:
(图1)
其中根部元素比叶子节点元素都要小,反之就是最大堆。
堆排序算法的基本思想就是利用堆的特性,保证每次取出来的根部元素是所有元素集合里面的最大元素(或者是最小元素)。取出根部元素后重新利用调整算法调整堆的结构使得其保持最大(最小)堆的形态。
现在明白了堆排序的原理下面一个要解决的问题就是怎么存储这个堆结构。传统存储一棵树的办法通常是用链表,但是那样会变的很复杂,其实如果我们仔细观察堆的结构就会发现他是一颗完全二叉树。图1就是一颗完全二叉树,从根节点开始从左到右给每层的节点进行编号后我们发现编号是连续的,9(0) 12(1) 11(2)33(3) 29(4),所以我们可以用一个数组来存储这棵树。对于每个节点i我们可以得到他的根节点的数组下标是(i-1)/2,这点很重要!
堆的存储示意图:
(图2)
现在我们搞清楚了堆的存储结构,那么问题就变得简单了,因为我们只是对一个数组操作,但是需要一点想象力,想象我们在对树操作。
堆的操作:
【1】插入
插入一个数据到堆当中,首先把这个数放到堆的末端,也就是数组的末端。查找他的父节点,比较两者的大小,如果不满足堆的定义,那就交换他们,否则不交换直接插入。
比如我们要在上面的堆中插入15,首先15的下标位置是5,根节点是(5-1)/2=2 也就是元素11,11比15小所以不发生改变。但是如果我们要插入6,同样先和根节点比较,6<11,所以交换他们,如下图:
(图3)
再次比较6和其根节点9发现还是不符合条件,所以再次交换他们的位置,发现6到达根部所以插入结束。
图4
【2】删除
搞懂上面的插入操作后其实删除的原理也是一样的,每次删除的都是堆根部的节点, 删除根部元素 后我们把最后一个元素X放到根部,然后我们的任务就是比较X是否小于他的两个子节点,如果满足不做修改,不满足则和其中较小的那个子节点交换,直到元素X找到自己的位置。比如上面的图4,删除6后,我们把11放到根节点的位置。把11和其叶子节点中较小的元素9相比,发现9较小,所以交换11和9。此时11的下标是最后一个元素,所以不再比较,调整完成。如果11的子节点依然比他小,那么继续交换,直到11比他的叶子节点都要小。
【3】初始化堆
堆的初始化也很简单,一直执行插入堆操作就可以得到初始化好的堆。
【4】堆排序
而堆排序实质就是不断的执行删除堆中元素的操作。