【排序算法】 堆排序 heap sort(选择类排序)

【排序算法】 堆排序 heap sort(选择类排序)

堆排序的过程

创建最大堆:

从最后一个非叶子结点到根结点(从下向上,这点非常关键)调整每一个子堆,使子堆有序化。
最开始的子堆的根结点是最后一个非叶子结点,它有一个或者两个孩子,它与较大者交换位置即可实现子堆有序化。
底部的子堆有序化,是其上面的堆进行调整的前提条件。
也即是,如果子堆是无序的,那么包含它的一个更大的堆就无法进行调整操作,
所以需要从最后一个非叶子结点到根结点(从下向上,这点非常关键)调整每一个子堆,使子堆有序化。。

调整最大堆(有序化堆):

(只有一个或者零个父结点相对孩子是无序)
如果当前结点有孩子结点,
如果当前结点的孩子结点的较大者比当前结点大,则交换两者位置,
否则调整结束,退出,
继续比较当前结点与其新的孩子结点

堆排序的步骤:

  1. 初始化最大堆(使堆有序化,可能所有的子堆都是无序的,需要调整所有的子堆)
  2. 将最大堆的第一个元素和最后一个元素交换位置;
    使堆的长度减 1;
    最大堆调整(使堆有序化,只有第一个元素相对于它的孩子是无序的,不断调整此元素和其后代的相对位置即可);
    重复以上三步,直到堆的长度为 1。

索引关系

使用数组存储完全二叉树(堆),则结点下标关系是:

父结点 i 的左子结点是 2 * i + 1;
父结点 i 的右子结点是 2 * i + 2;
子结点 i 的父结点是 floor((i-1)/2)
后一个非叶子结点是 floor(N/2)-1

时间复杂度

expense:
时间复杂度 O(N*log(N))

应用场景

堆排序实现

package algorithm.algorithm4.ch02_sort;

import org.junit.Test;

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

/**
 * algorithm: heap sort,堆排序
 */
public class Heap {

    public static void sort (Comparable[] arr){
        if (arr == null || arr.length < 2) return;

        // 1. 创建堆
        // 从下向上使每一个有孩子的结点都成为一个有序堆(宏观角度)
        // 多次堆化,从下到上不断新加元素,因为单次堆化要求最多只有根结点相对于孩子结点是无序的,所以需要从后往前添加元素
        // 具体的堆化操作是从上到下完成的,而且一次堆化要求最多只有根结点相对于孩子结点是无序的,此外所有的子堆都是有序的
        for (int i = arr.length/2-1; i >= 0; i --)  // 完全二叉树中最后一个有孩子的结点是 N/2-1
            maxHeapify(arr, i, arr.length);

        // 2. 堆排序
        for (int j = arr.length - 1; j > 0; j --) {
            swap(arr, 0, j);  // 堆首元素不断和堆尾元素交换位置,堆的大小减 1
            maxHeapify(arr, 0, j);  // 将去掉一个元素的新堆调整为最大堆(有序堆)
        }
    }


    /**
     * 从上到下有序化堆,使小于孩子的父结点下沉(sink)
     * @param arr 二叉堆
     * @param index 待调整的子堆的根结点
     * @param newSize 除了排序后的元素外,堆中剩余元素的个数
     */
    // 具体的堆化操作是从上到下完成的,而且一次堆化要求最多只有根结点相对于孩子结点是无序的,此外所有的子堆都是有序的
    private static void maxHeapify (Comparable[] arr, int index, int newSize){

        while (2 * index + 1 < newSize){  // 当前结点至少有左孩子
            int childIdx = 2 * index + 1;  // index 左孩子的下标
            // 存在右孩子,且右孩子比左孩子更大,则记录右孩子的下标
            if (childIdx + 1 < newSize && arr[childIdx].compareTo(arr[childIdx+1]) < 0) childIdx ++;
            if (arr[childIdx].compareTo(arr[index]) <= 0) break;  // 两个孩子均小于等于当前元素,则退出循环
            swap(arr, index, childIdx);
            index = childIdx;  // 当前不断跟新的孩子比较,跟其中一个较大的孩子交换位置
        }
    }


    private static void swap(Comparable[] arr, int i, int j){
        Comparable tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }


    @Test
    public void test(){
        Random random = new Random();
        int N = 20;
        Integer[] arr = new Integer[N];
        for (int i = 0; i < N; i ++) arr[i] = random.nextInt(100);
        System.out.println(Arrays.toString(arr));
        Heap.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值