图文并茂!一文教你掌握十大排序算法之堆排序

1. 堆排序算法原理及思想

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。它的最好、最坏和平均时间复杂度都为O(N*logN),它是一种不稳定排序,下面了解堆这种数据结构。

堆是一种特殊的完全二叉树,分为大顶堆和小顶堆;在原有的二叉树的性质上,大顶堆还需要严格保证父节点的值大于等于孩子节点的值;小顶堆则严格保证父节点的值小于等于孩子节点的值。

堆只是一个逻辑结构,本质上还是数组。如果用数组表示堆这个结构,大顶堆和小顶堆必须满足的条件用数组表示为:

大顶堆:arr[i] ≥ arr[2i+1] && arr[i] ≥ arr[2i+2]

小顶堆:arr[i] ≤ arr[2i+1] && arr[i] ≤ arr[2i+2]

如果要将数组升序排序则构造大顶堆,如果需要降序则构造小顶堆。

构造大顶堆的目的是为了每一轮排序确定一个最大值并将其放到堆的尾部(数组的尾部)

构造小顶堆的目的是为了每一轮排序确定一个最小值并将其放到堆的尾部(数组的尾部)

2. 堆排序算法步骤

以升序为例,第一步是把数组构造成符合大顶堆的状态:从下往上、从右往左开始判断非叶子节点是否符合大顶堆的结构,这里运用到完全二叉树的一个性质:第一个非叶子节点的数组下标为:index = (arr.length / 2) - 1

2.1 构造大顶堆

【下图左上】以数组[33, 35, 41, 26, 78]为例,第一个非叶子节点的下标index = (5 / 2) - 1 = 1,所以35是第一个非叶子节点,判断以35为根节点的完全二叉树是否满足大顶堆的条件,发现35 < 78,所以交换位置,数组更新为[33, 78, 41, 26, 35],由于35是第一个非叶子节点,所以它的孩子节点必定是叶子节点,无需继续判断是否满足大顶堆的条件。

【下图右上】然后index--,index = 0,判断33为根节点的完全二叉树是否满足大顶堆的条件,发现33 < 78,所以交换位置,数组更新为[78, 33, 41, 26, 35]。

【下图右下】由于33与78交换了位置,破坏了78在index = 1时作为根节点的大顶堆,需要再判断33为根节点的完全二叉树是否符合大顶堆条件。33 < 35,所以交换位置,数组更新为[78, 35, 41, 26, 33],此时已经到了第一个非叶子节点,不需要继续判断。

【下图左下】大顶堆构建完成。

2.2 首尾元素交换并重建大顶堆

下图使第一轮的堆排序,将堆顶元素与堆尾元素交换,使堆尾元素最大,然后重新调整堆结构,再将堆顶元素与堆尾元素互换,得出第二大的元素,然后反复的交换、调整、交换、调整。

注意每次需要重建的堆元素不应该包括刚刚交换的堆尾元素,因为最大的元素已经沉到堆的底部了。【下图绿色元素78不参与重建大顶堆】

3. 堆排序思路总结

1)根据数组需要升序/降序排列构造大顶堆/小顶堆。

2)将堆首元素和堆尾元素互换,使得最大/最小值沉到堆的底部(数组末端)。

3)重新调整堆的结构

4)重复第2、3步,直至整个数组有序。

4. 代码实现

/**
 * @author Zeng
 * @date 2020/3/3 22:28
 */
public class 堆排序1 {

    public static void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void heapSort(int[] arr){
        //构造大顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--){
            adjustHeap(arr, i, arr.length);
        }
        //堆顶元素与堆尾元素作交换
        for (int j = arr.length - 1; j >= 0; j--){
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        }
    }

    public static void adjustHeap(int[] arr, int begin, int end){
        for (int i = 2 * begin + 1; i < end; i=2*begin+1){
            //如果右孩子比左孩子大就将指针指向右孩子
            if(i + 1 < end && arr[i] < arr[i + 1]){
                i++;
            }
            //如果父亲节点比孩子节点小则交换它们的位置,并且将指针指向孩子节点,下一步继续构造以孩子节点为根节点的大顶堆
            if(arr[begin] < arr[i]){
                swap(arr, begin, i);
                begin = i;
            }else{
                //父节点比左右孩子节点都大,则说明已经符合大顶堆的要求
                break;
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{33, 35, 41, 26, 78};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

}
发布了50 篇原创文章 · 获赞 46 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_41949328/article/details/104649288