二叉堆的构建,删除节点以及自我调整等基础的操作是实现堆排序的基础(详看上一篇)。
在最大堆中,如果删除了堆顶元素,然后经过自我调整后,第二大的元素就会被交换上来,称为最大堆的新堆顶。(在堆排序中的删除,并不是真正的删除,而是将其与完全二叉树最后的一个元素交换位置)。例如下图分析:
对于上图,黑色节点为每次最大堆的堆顶与最末尾元素换下来的元素,红色节点为每次换完调整后的最大堆的最大值。
从上可分析排序算法步骤:
- 先将无序数组构建成二叉堆,需要从小到大排序,构建最大堆;需要从大到小排序,则构建最小堆。
- 循环删除堆顶元素,替换到二叉堆的末尾,然后重新构建堆产生新的堆顶。
具体实现:
/** *将无序数组构建成二叉堆,所需要的时间复杂度是O(n),而每次交换完都需要重新调整构建,需要(n-1)logn,所以时间复杂度是 O(nlogn)。 * */ public class StackSort { public static void main(String[] args) { int[] array = new int[]{1,5,6,3,2,8,7,9}; // 构建堆 for (int i = (array.length - 2)/2; i >= 0; i--) { downAdjust(array,i,array.length); } //交换首节点和末尾节点 for (int i = array.length - 1; i > 0; i--) { int tail = array[i]; array[i] = array[0]; array[0] = tail; downAdjust(array,0,i); //重新调整堆 } System.out.println(Arrays.toString(array)); } /** * "下沉"节点构建大根堆 * @param array 待调整数组 * @param i 根结点 * @param length 有效的位数 */ private static void downAdjust(int[] array, int i, int length) { int parentIndex = i; //父节点下标 int childIndex = 2*parentIndex+1; //左孩子节点 int temp = array[parentIndex]; //临时保存父节点的值 while (childIndex < length){ //存在左孩子 // 如果存在右孩子,并且右孩子比左孩子大的话,定位到右孩子 if(childIndex+1 < length && array[childIndex+1] > array[childIndex]){ childIndex = childIndex+1; } // 如果父节点大于左右孩子中最大的,直接退出 if(temp > array[childIndex]){ break; } //否则 array[parentIndex] = array[childIndex]; //每次比较都交换,好理解(相对于最终赋值) array[childIndex] = temp; // 更新结点 parentIndex = childIndex; //向下比较,大的上浮 childIndex = 2*parentIndex+1; } // 最终赋值(不需要每次比较都交换,每次只是向下覆盖,只是最终实现赋值)--->推荐 // array[parentIndex] = temp; } }