算法简介
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
算法描述和实现
具体算法描述如下:
- <1>.将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
- <2>.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
- <3>.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后 再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元 素个数为n-1,则整个排序过程完成。
Javascript代码实现:
/*方法说明:堆排序
@param array 待排序数组*/
function heapSort(array) {
console.time('堆排序耗时');
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
//建堆
var heapSize = array.length, temp;
for (var i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
heapify(array, i, heapSize);
}
//堆排序
for (var j = heapSize - 1; j >= 1; j--) {
temp = array[0];
array[0] = array[j];
array[j] = temp;
heapify(array, 0, --heapSize);
}
console.timeEnd('堆排序耗时');
return array;
} else {
return 'array is not an Array!';
}
}
/*方法说明:维护堆的性质
@param arr 数组
@param x 数组下标
@param len 堆大小*/
function heapify(arr, x, len) {
if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array' && typeof x === 'number') {
var l = 2 * x + 1, r = 2 * x + 2, largest = x, temp;
if (l < len && arr[l] > arr[largest]) {
largest = l;
}
if (r < len && arr[r] > arr[largest]) {
largest = r;
}
if (largest != x) {
temp = arr[x];
arr[x] = arr[largest];
arr[largest] = temp;
heapify(arr, largest, len);
}
} else {
return 'arr is not an Array or x is not a number!';
}
}
var arr=[91,60,96,13,35,65,46,65,10,30,20,31,77,81,22];
console.log(heapSort(arr));//[10, 13, 20, 22, 30, 31, 35, 46, 60, 65, 65, 77, 81, 91, 96]
JAVA:
package flyingcat.sort;
/**
*
* @author FlyingCat
* Date: 2013-8-26
*
*/
public class ArrayHeap {
private int[] array;
public ArrayHeap(int[] arr) {
this.array = arr;
}
private int getParentIndex(int child) {
return (child - 1) / 2;
}
private int getLeftChildIndex(int parent) {
return 2 * parent + 1;
}
/**
* 初始化一个大根堆。
*/
private void initHeap() {
int last = array.length - 1;
for (int i = getParentIndex(last); i >= 0; --i) { // 从最后一个非叶子结点开始筛选
int k = i;
int j = getLeftChildIndex(k);
while (j <= last) {
if (j < last) {
if (array[j] <= array[j + 1]) { // 右子节点更大
j++;
}
}
if (array[k] > array[j]) { //父节点大于子节点中较大者,已经找到最终位置
break; // 停止筛选
} else {
swap(k, j);
k = j; // 继续筛选
}
j = getLeftChildIndex(k);
}// loop while
}// loop i
}
/**
* 调整堆。
*/
private void adjustHeap(int lastIndex) {
int k = 0;
while (k <= getParentIndex(lastIndex)) {
int j = getLeftChildIndex(k);
if (j < lastIndex) {
if (array[j] < array[j + 1]) {
j++;
}
}
if (array[k] < array[j]) {
swap(k, j);
k = j; // 继续筛选
} else {
break; // 停止筛选
}
}
}
/**
* 堆排序。
* */
public void sort() {
initHeap();
int last = array.length - 1;
while (last > 0) {
swap(0, last);
last--;
if (last > 0) { // 这里如果不判断,将造成最终前两个元素逆序。
adjustHeap(last);
}
}
}
private void swap(int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
算法性能/复杂度
堆排序的时间复杂度非常稳定(我们可以看到,对输入数据不敏感),为O(n㏒n)复杂度,最好情况与最坏情况一样。
但是,其空间复杂度依实现不同而不同。上面即讨论了两种常见的复杂度:O(n)与O(1)。本着节约空间的原则,我推荐O(1)复杂度的方法。
算法稳定性
堆排序存在大量的筛选和移动过程,属于不稳定的排序算法。
算法适用场景
堆排序在建立堆和调整堆的过程中会产生比较大的开销,在元素少的时候并不适用。但是,在元素比较多的情况下,还是不错的一个选择。尤其是在解决诸如“前n大的数”一类问题时,几乎是首选算法。
算法分析
- 最佳情况:T(n) = O(nlogn)
- 最差情况:T(n) = O(nlogn)
- 平均情况:T(n) = O(nlogn)