堆排序
二叉堆的特性:
最大堆的堆顶是整个堆中的最大元素;最小堆的堆顶是整个堆中的最小元素。
以最大堆为例,如果删除一个最大堆的堆顶(并不是完全删除,而是跟末尾的节点交换位置),经过自我调整,第2大的元素就会被交换上来,成为最大堆的新堆顶。慢慢地进行这种删除,得到的序列就是一个从大到小的序列,而较大的一头先交换到了数组末尾一端,较小的则留在了数组头部一端。这样就实现了排序。
堆排序算法的步骤:
把无序数组构建成二叉堆。需要从小到大排序,则构建成最大堆;需要从大到小排序,则构建成最小堆。
循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶。
上代码:
/**
* 下沉调整
* @param array 待调整的堆
* @param parentIndex 要下沉的父节点
* @param length 堆的有效大小
*/
public static void downAdjust(int[] array, int parentIndex, int length) {
// temp保存父节点值,用于最后的赋值
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1;
while (childIndex < length) {
// 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]) {
childIndex++;
}
// 如果父节点大于等于任何一个孩子的值,直接跳出
if (temp >= array[childIndex])
break;
//无需真正交换,单向赋值即可
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
/**
* 堆排序(升序)
* @param array 待调整的堆
*/
public static void heapSort(int[] array) {
// 1.把无序数组构建成最大堆。
for (int i = (array.length-2)/2; i >= 0; i--) {
downAdjust(array, i, array.length);
}
System.out.println(Arrays.toString(array));
// 2.循环交换集合尾部元素到堆顶,并调节堆产生新的堆顶。
for (int i = array.length - 1; i > 0; i--) {
// 最后一个元素和第一元素进行交换
int temp = array[i];
array[i] = array[0];
array[0] = temp;
// 下沉调整最大堆
downAdjust(array, 0, i);//这儿的i是值堆有效长度
}
}
public static void main(String[] args) {
int[] arr = new int[] {1,3,2,6,5,7,8,9,10,0};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
空间复杂度:是O(1),因为并没有开辟额外的集合空间。
时间复杂度:分析如下
- 把无序数组构建成二叉堆,时间复杂度是O(n)。
- 循环删除堆顶元素,并将该元素移到集合尾部,调整堆产生新的
堆顶。需要进行n-1次循环。每次循环调用一次downAdjust方法,
所以第2步的计算规模是 (n-1)×logn ,时间复杂度为O(nlogn)。
因此总的运算量是n+nlogn,留下系数较高的一项nlogn,则总的时间复杂度是O(nlogn)。