归并排序的平均时间复杂度为O(NlogN),但是要占用空间,因此效率上并没有提升多少。
快速排序的平均时间复杂度为O(NlogN),但是最坏的情况,时间复杂度还是会到O(N^2)。
因此能不能有一种排序,时间复杂度为O(NlogN),最坏的情况时间复杂度也是O(NlogN)呢?
利用优先级队列中二叉堆的性质,我们考虑把一串无序的序列构建成堆,然后依次把最顶上的元素放到数组的最末尾,这样数组就可以从小到大排好序了。
因此我们采取两步法来实现堆排序
第一步:建堆
建堆的目的是为了把那些父节点比子节点小的元素沉降到它的相应位置。因此我们应该从父节点的最后一位开始依次向上遍历,直到顶点为止。
定理:父节点的最后一位 i 一定是元素总数N的一半,即i=N/2。利用的就是子节点=父节点*2或子节点=父节点*2+1。
建堆的过程是让N/2个元素依次沉降到相应位置,所以时间复杂度是(N/2)*logN
int N = array.length-1;
// heap construction
for (int i = N / 2; i >= 1; i--)
sink(array, i, N);
第二步:堆排序
把最顶点和最后一位元素交换,然后将换到顶点的新元素沉降到合适位置。
时间复杂度是NlogN
while (N > 1) {
exchange(array, 1, N--);
sink(array, 1, N);
}
这样堆排序就结束了。
以下是sink函数的实现
public static void sink(int[] array, int k, int N) {
// N is the number of disorder elements
while (2 * k <= N) {
int j = 2 * k;
if (j < N && more(array[j + 1], array[j]))
j++;
if (more(array[k], array[j]))
break;
exchange(array, j, k);
k = j;
}
}
public static boolean more(int v, int w) {
return v > w;
}
public static void exchange(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
通过堆排序,我们发现它几乎完成了时间复杂度为O(NlogN),最坏的情况时间复杂度也是O(NlogN)的效果,但是有一个问题在于这个排序依旧是一个不稳定的排序,能不能发明一个稳定的排序且有同样的效果,我们只能等待大师们的灵感了!