在普通堆排序中,原始的数组必须要先添加进一个maxHeap堆,再将堆中的元素取出来放到一个temp数组,这个堆和数组都需要消耗额外的空间,是非原地排序,复杂度都是O(nlogn)

因此可以对这两个过程进行优化:

  1. 不用将原始数组添加进一个额外的堆,而是直接将原始数组看成一个堆,用heapify()方法让其满足最大堆的条件,这种方法只考虑非叶子节点,省去了一半的元素,时间复杂度为O(n)
  2. 不用将堆中的元素取出来放在一个额外的数组,而是直接将这个堆按照从小到大的顺序进行原地排序,因为第一个元素始终是最大的,将第一个和最后一个元素互换,即最大值放到数组末尾,然后将堆的范围减1变为[0, size - 1),再将第一个元素进行siftDown,让左边n - 1个元素恢复成最大堆,继续进行以上操作,最后的数组就是升序排列的,时间复杂度为O(n)

heapify()方法性能比较

import java.util.Random;

public class Algorithm {

    public static void main(String[] args) {

        int n = 10000000;
        Random random = new Random();
        Integer[] testData = new Integer[n];

        for (int i = 0; i < n; i++) {
            testData[i] = random.nextInt(Integer.MAX_VALUE);
        }

        testHeap(testData, false);
        testHeap(testData, true);
    }

    public static void testHeap(Integer[] testData, boolean isHeapify){

        long startTime = System.nanoTime();
        MaxHeap<Integer> maxHeap;

        if (isHeapify){

            maxHeap = new MaxHeap<>(testData);
            long endTime = System.nanoTime();

            System.out.println("heapify: " + (endTime - startTime) / 1000000000.0 + "秒");
        }
        else {

            maxHeap = new MaxHeap<>();
            for (int i : testData){
                maxHeap.add(i);
            }

            long endTime = System.nanoTime();

            System.out.println("simple: " + (endTime - startTime) / 1000000000.0 + "秒");
        }

        int[] temp = new int[testData.length];

        for (int i = 0; i < testData.length; i++) {
            temp[i] = maxHeap.extractMax();
        }

        for (int i = 0; i < arr.length - 1; i++) {

            if (temp[i] < temp[i + 1]){
                throw new IllegalArgumentException("二叉堆实现失败");
            }
        }
    }
}

class MaxHeap<E extends Comparable<E>>{

    private Array<E> heap;

    public MaxHeap(int capacity){

        heap = new Array<>(capacity);
    }

    public MaxHeap(){

        heap = new Array<>(10);
    }

    /**
     * 添加一个构造方法,用heapify()方法直接将一个原始数组变成最大堆
     */
    public MaxHeap(E[] arr){

        heap = new Array<>(arr);
        heapify(arr);
    }

    public int size(){

        return heap.getSize();
    }

    public boolean isEmpty(){

        return size() == 0;
    }

    private int parent(int index){

        if (index == 0) {
            throw new IllegalArgumentException("根节点没有父节点");
        }

        return (index - 1) / 2;
    }

    private int leftChild(int index){

        return 2 * index + 1;
    }

    private int rightChild(int index){

        return 2 * index + 2;
    }

    public void add(E e){

        heap.addLast(e);
        siftUp(size() - 1);
    }

    private void siftUp(int index){

        while (index > 0 && heap.get(index).compareTo(heap.get(parent(index))) > 0){

            heap.swap(index, parent(index));
            index = parent(index);
        }
    }

    public E findMax(){

        if (size() == 0){
            throw new IllegalArgumentException("堆为空");
        }

        return heap.get(0);
    }

    public E extractMax(){

        E max = findMax();

        heap.swap(0, size() - 1);
        heap.remove(size() - 1);
        siftDown(0);

        return max;
    }

    private void siftDown(int index){

        while (leftChild(index) < size()){

            int max = leftChild(index);

            if (rightChild(index) < size() && heap.get(leftChild(index)).compareTo(heap.get(rightChild(index))) < 0){
                max = rightChild(index);
            }

            if (heap.get(index).compareTo(heap.get(max)) >= 0){
                break;
            }

            heap.swap(index, max);
            index = max;
        }
    }

    /**
     * heapify()方法将数组看成是一个不满足条件的堆
     * 从最后一个非叶子节点开始,依次进行SiftDown操作,直到成为最大堆
     * 最后一个非叶子节点,就是最后一个节点的父节点
     */
    public void heapify(E[] arr){

        if (arr.length > 1){
            
            for (int i = parent(arr.length - 1); i >= 0; i--) {
                siftDown(i);
            }
        }
    }

    /**
     * replace()方法替换最大节点,即根节点
     * 先保存根节点,将根节点直接替换,然后再用SiftDown()方法满足条件
     */
    public E replace(E e){

        E max = findMax();
        heap.set(e, 0);
        siftDown(0);

        return max;
    }
}

class Array<E>{

    private E[] data;
    private int size;

    public Array(int capacity){

        data = (E[]) new Object[capacity];
        size = 0;
    }

    /**
     * 添加一个构造方法,可以直接传入一个数组
     */
    public Array(E[] arr){

        data = (E[]) new Object[arr.length];

        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }

        size = arr.length;
    }

    public Array(){

        data = (E[]) new Object[10];
        size = 0;
    }

    public int getSize(){

        return size;
    }

    public void swap(int index1, int index2){

        E temp;
        temp = data[index1];
        data[index1] = data[index2];
        data[index2] = temp;
    }

    public E get(int index){

        if (index < 0 || index >= size){
            throw new IllegalArgumentException("索引值非法");
        }

        return data[index];
    }

    public void set(E e, int index){

        if (index < 0 || index >= size){
            throw new IllegalArgumentException("索引值非法");
        }

        data[index] = e;
    }

    public void add(E e, int index){

        if (index < 0 || index > size){
            throw new IllegalArgumentException("索引值非法");
        }

        if (size == data.length){
            resize(2 * data.length);
        }

        for (int i = size - 1; i >= index; i++) {
            data[i + 1] = data[i];
        }

        data[index] = e;
        size++;
    }

    public void addLast(E e){
        add(e, size);
    }

    public void remove(int index){

        if (index < 0 || index >= size){
            throw new IllegalArgumentException("索引值非法");
        }

        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }

        size--;
        data[size] = null;

        if (size == data.length / 2 && data.length / 2 != 0){
            resize(data.length / 2);
        }
    }

    public void resize(int newCapacity){

        E[] temp = (E[])new Object[newCapacity];

        for (int i = 0; i < size; i++) {
            temp[i] = data[i];
        }

        data = temp;
    }

    @Override
    public String toString(){

        StringBuilder str = new StringBuilder();
        str.append("[");

        for (int i = 0; i < size; i++) {

            str.append(data[i]);
            if (i != size - 1){
                str.append(", ");
            }
        }

        str.append("]");

        return str.toString();
    }
}

优化的原地堆排序性能比较

import java.util.Arrays;
import java.util.Random;

public class Algorithm {

    public static void main(String[] args) {

        Integer[] testScale = {1000000, 10000000};

        for (Integer n : testScale){

            Integer[] randomArr = ArrayGenerator.generatorRandomArray(n, n);
            Integer[] sortedArr = ArrayGenerator.generatorSortedArray(n, n);

            Integer[] arr1 = Arrays.copyOf(randomArr, randomArr.length);
            Integer[] arr3 = Arrays.copyOf(randomArr, randomArr.length);
            Integer[] arr5 = Arrays.copyOf(randomArr, randomArr.length);
            Integer[] arr7 = Arrays.copyOf(randomArr, randomArr.length);

            Integer[] arr2 = Arrays.copyOf(sortedArr, sortedArr.length);
            Integer[] arr4 = Arrays.copyOf(sortedArr, sortedArr.length);
            Integer[] arr6 = Arrays.copyOf(sortedArr, sortedArr.length);
            Integer[] arr8 = Arrays.copyOf(sortedArr, sortedArr.length);

            System.out.println("测试随机数组排序性能");
            System.out.println();

            Verify.testTime("HeapSort", arr1);
            Verify.testTime("HeapSortOptimized", arr3);
            Verify.testTime("QuickSort3Ways", arr5);
            Verify.testTime("MergeSort", arr7);

            System.out.println();

            System.out.println("测试有序数组排序性能");
            System.out.println();

            Verify.testTime("HeapSort", arr2);
            Verify.testTime("HeapSortOptimized", arr4);
            Verify.testTime("QuickSort3Ways", arr6);
            Verify.testTime("MergeSort", arr8);

            System.out.println();
        }

    }
}

class HeapSort<E>{

    private HeapSort(){}

    public static<E extends Comparable<E>> void sort(E[] arr){

        MaxHeap<E> maxHeap = new MaxHeap<>();

        for (E e: arr){
            maxHeap.add(e);
        }

        for (int i = arr.length - 1; i >= 0; i--) {
            arr[i] = maxHeap.extractMax();
        }
    }

    /**
     * 原地堆排序
     * 因为不需要创建MaxHeap类对象,所以重写了heapify()方法实现一个最大堆
     * 然后对arr[0, range)范围内的元素进行siftDown
     * siftDown()方法默认的数组范围是n,因此需要重新修改一些地方
     */
    public static<E extends Comparable<E>> void sortOptimized(E[] arr){

        heapify(arr);

        for (int i = arr.length - 1; i >= 0; i--) {

            swap(arr, 0, i);
            siftDown(arr, 0, i);
        }
    }

    public static<E extends Comparable<E>> void heapify(E[] arr){

        if (arr.length > 1){

            for (int i = (arr.length - 2) / 2; i >= 0; i--) {
                siftDown(arr, i, arr.length);
            }
        }
    }

    public static<E extends Comparable<E>> void siftDown(E[] arr, int index, int range){

        while (2 * index + 1 < range) {

            int max = 2 * index + 1;

            if (max + 1 < range && arr[max].compareTo(arr[max + 1]) < 0) {
                max++;
            }

            if (arr[index].compareTo(arr[max]) >= 0) {
                break;
            }

            swap(arr, index, max);
            index = max;
        }
    }

    public static<E extends Comparable<E>> void swap(E[] arr, int index1, int index2){

        E temp;
        temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }
}

class QuickSort {

    private QuickSort() {
    }

    public static<E extends Comparable<E>> void sort3ways(E[] arr){

        Random random = new Random();
        E temp = null;
        sort3ways(arr, 0, arr.length - 1, temp, random);
    }

    public static<E extends Comparable<E>> void sort3ways(E[] arr, int left, int right, E temp, Random random){

        if (left >= right){

            return;
        }

        int[] res = partition3ways(arr, left, right, temp, random);

        sort3ways(arr, left, res[0], temp, random);
        sort3ways(arr, res[1], right, temp, random);
    }

    public static<E extends Comparable<E>> int[] partition3ways(E[] arr, int left, int right, E temp, Random random){

        int p = random.nextInt(right - left + 1) + left;

        swap(arr, p, left, temp);

        int i = left + 1;
        int lt = left;
        int gt = right + 1;

        while (i < gt){

            if (arr[i].compareTo(arr[left]) < 0){

                lt++;
                swap(arr, lt, i, temp);
                i++;
            }

            else if (arr[i].compareTo(arr[left]) == 0){
                i++;
            }

            else if (arr[i].compareTo(arr[left]) > 0){
                gt--;
                swap(arr, gt, i, temp);
            }
        }

        swap(arr, lt, left, temp);
        int[] res = {lt - 1, gt};

        return res;
    }

    public static<E extends Comparable<E>> void swap(E[] arr, int i, int j, E temp){

        temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

class MergeSort {

    private MergeSort(){}

    public static<E extends Comparable<E>> void sort(E[] arr){

        E[] temp = Arrays.copyOf(arr, arr.length);
        sort(arr, 0, arr.length - 1, temp);
    }

    private static<E extends Comparable<E>> void sort(E[] arr, int left, int right, E[] temp){

        if (left >= right){

            return;
        }

        int mid = left + (right - left) / 2;

        sort(arr, left, mid, temp);
        sort(arr, mid + 1, right, temp);

        if (arr[mid].compareTo(arr[mid + 1]) > 0) {
            merge(arr, left, mid, right, temp);
        }
    }

    public static<E extends Comparable<E>> void merge(E[] arr, int left, int mid, int right, E[] temp){

        int i = left;
        int j = mid + 1;

        System.arraycopy(arr, left, temp, left, right - left + 1);

        for (int n = left; n < right + 1; n++) {

            if (i == mid + 1){

                arr[n] = temp[j];
                j++;
            }
            else if (j == right + 1) {

                arr[n] = temp[i];
                i++;
            }
            else if (temp[i].compareTo(temp[j]) <= 0) {

                arr[n] = temp[i];
                i++;
            }
            else{

                arr[n] = temp[j];
                j++;
            }
        }
    }
}

class MaxHeap<E extends Comparable<E>>{

    private Array<E> heap;

    public MaxHeap(int capacity){

        heap = new Array<>(capacity);
    }

    public MaxHeap(){

        heap = new Array<>(10);
    }

    public MaxHeap(E[] arr){

        heap = new Array<>(arr);
        heapify(arr);
    }

    public int size(){

        return heap.getSize();
    }

    public boolean isEmpty(){

        return heap.getSize() == 0;
    }

    public int parent(int index){

        if (index == 0) {
            throw new IllegalArgumentException("根节点没有父节点");
        }
        return (index - 1) / 2;
    }

    private int leftChild(int index){

        return 2 * index + 1;
    }

    private int rightChild(int index){

        return 2 * index + 2;
    }

    public void add(E e){

        heap.addLast(e);
        siftUp(size() - 1);
    }

    private void siftUp(int index){

        while (index > 0 && heap.get(index).compareTo(heap.get(parent(index))) > 0){

            heap.swap(index, parent(index));
            index = parent(index);
        }
    }

    public E findMax(){

        if (size() == 0){
            throw new IllegalArgumentException("堆为空");
        }

        return heap.get(0);
    }

    public E extractMax(){

        E max = findMax();

        heap.swap(0, size() - 1);
        heap.remove(size() - 1);
        siftDown(0);

        return max;
    }

    public void siftDown(int index){

        while (leftChild(index) < size()){

            int max = leftChild(index);

            if (rightChild(index) < size() && heap.get(leftChild(index)).compareTo(heap.get(rightChild(index))) < 0){
                max = rightChild(index);
            }

            if (heap.get(index).compareTo(heap.get(max)) >= 0){
                break;
            }

            heap.swap(index, max);
            index = max;
        }
    }

    public void heapify(E[] arr){

        if (arr.length > 1){
            for (int i = parent(arr.length - 1); i >= 0; i--) {
                siftDown(i);
            }
        }
    }

    public E replace(E e){

        E max = findMax();
        heap.set(e, 0);
        siftDown(0);

        return max;
    }
}

class Array<E>{

    private E[] data;
    private int size;

    public Array(int capacity){

        data = (E[]) new Object[capacity];
        size = 0;
    }

    public Array(E[] arr){

        data = (E[]) new Object[arr.length];

        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }

        size = arr.length;
    }

    public Array(){

        data = (E[]) new Object[10];
        size = 0;
    }

    public int getSize(){

        return size;
    }

    public void swap(int index1, int index2){

        E temp;
        temp = data[index1];
        data[index1] = data[index2];
        data[index2] = temp;
    }

    public E get(int index){

        if (index < 0 || index >= size){
            throw new IllegalArgumentException("索引值非法");
        }

        return data[index];
    }

    public void set(E e, int index){

        if (index < 0 || index >= size){
            throw new IllegalArgumentException("索引值非法");
        }

        data[index] = e;
    }

    public void add(E e, int index){

        if (index < 0 || index > size){
            throw new IllegalArgumentException("索引值非法");
        }

        if (size == data.length){
            resize(2 * data.length);
        }

        for (int i = index - 1; i >= index; i++) {
            data[i + 1] = data[i];
        }

        data[index] = e;
        size++;
    }

    public void addLast(E e){
        add(e, size);
    }

    public void remove(int index){

        if (index < 0 || index >= size){
            throw new IllegalArgumentException("索引值非法");
        }

        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }

        size--;
        data[size] = null;

        if (size == data.length / 2 && data.length / 2 != 0){
            resize(data.length / 2);
        }
    }

    public void resize(int newCapacity){

        E[] temp = (E[])new Object[newCapacity];

        for (int i = 0; i < size; i++) {
            temp[i] = data[i];
        }

        data = temp;
    }

    @Override
    public String toString(){

        StringBuilder str = new StringBuilder();
        str.append("[");

        for (int i = 0; i < size; i++) {

            str.append(data[i]);

            if (i != size - 1){
                str.append(", ");
            }
        }

        str.append("]");

        return str.toString();
    }
}

class ArrayGenerator {

    private ArrayGenerator (){}

    public static Integer[] generatorRandomArray (Integer n, Integer maxBound){

        Integer[] arr = new Integer[n];
        Random random = new Random();

        for (int i = 0; i < n; i++) {
            arr[i] = random.nextInt(maxBound);
        }

        return arr;
    }

    public static Integer[] generatorSortedArray (Integer n, Integer maxBound){

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++) {
            arr[i] = i;
        }

        return arr;
    }
}

class Verify {

    private Verify (){}

    public static<E extends Comparable<E>> boolean isSorted(E[] arr){

        for (int i = 0; i < arr.length - 1; i++) {

            if (arr[i].compareTo(arr[i + 1]) > 0) {
                return false;
            }
        }

        return true;
    }

    public static<E extends Comparable<E>> void testTime(String AlgorithmName, E[] arr) {

        long startTime = System.nanoTime();

        if (AlgorithmName.equals("HeapSort")) {
            HeapSort.sort(arr);
        }

        if (AlgorithmName.equals("HeapSortOptimized")) {
            HeapSort.sortOptimized(arr);
        }

        if (AlgorithmName.equals("QuickSort3Ways")) {
            QuickSort.sort3ways(arr);
        }

        if (AlgorithmName.equals("MergeSort")) {
            MergeSort.sort(arr);
        }

        long endTime = System.nanoTime();

        if (!Verify.isSorted(arr)){
            throw new RuntimeException(AlgorithmName + "算法排序失败!");
        }

        System.out.println(String.format("%s算法,测试用例为%d,执行时间:%f秒", AlgorithmName, arr.length, (endTime - startTime) / 1000000000.0));
    }
}