堆与堆排序

堆与堆排序



堆(二叉堆)

定义描述:

  • 堆的逻辑结构为完全二叉树在物理存储上一般表示为一种数组对象

  • 数组中的数据按照其逻辑结构树的广度优先算法(队列优先)来存储对应的值。

性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;

    即:n个元素的序列{k1,k2,ki,…,kn}。       κ i \kappa_i κi ≤ \leq κ 2 i \kappa_{2i} κ2i && κ i \kappa_i κi ≤ \leq κ 2 i + 1 \kappa_{2i+1} κ2i+1 (小根堆)

    ​                                                                κ i \kappa_i κi ≥ \geq κ 2 i \kappa_{2i} κ2i && κ i \kappa_i κi ≥ \geq κ 2 i + 1 \kappa_{2i+1} κ2i+1 (大根堆)

  • 堆总是一棵完全二叉树。树的每一层都是填满的,最后一层可能除外(最后一个从左子树填写值)。

  • 堆的数组A是一个具有两个属性的对象:length[A] 是数组元素个数,heap-size[A] 是存放在A中堆元素个数。

    故:A[ 0, length[A]-1 ] 中都可以包含有效值, 但是A[ heap-size[A] - 1 ]之后的元素都不属于相应的堆

    heap-size[A] <= length[A]

  • 树中的每个结点与数组中存放该结点值的那个元素对应。

    heap1

因此 父节点和左右节点的关系如下:
  • Parent(i) = floor((i-1)/2), i 的父节点下标 floor 向下取整
  • Left(i) = 2i+1, i 的左子节点下标
  • Right(i) = 2(i+1) , i 的右子节点下标

堆的高度:

堆中的高度定义为从本结点到叶子的最长简单下降路径上边的数目

堆的高度为树根的高度。具有n个元素的堆是基于一棵完全二叉树的,因而其高度为O(lgn)。


思维拓展:

堆这种数据结构是处理海量数据比较常见的结构,海量数据的TOP K问题一般可以通过分治法+hash+堆这种数据结构解决。

  • 比如:
    在spark shuffle 中的 SortShuffleWriter 就是使用了 堆的特性, 进行堆排序,实现大数据量的排序的。



堆排序

定义:

  • 利用堆这种数据结构所设计的一种排序算法。

堆排序流程:

  • 构建最大堆:

    • 使单个子堆成为最大堆(Max-Heapify)

    • 自下而上的调用 Max-Heapify 来改造数组, 使整个数组改造成一个最大堆。

      构建最大堆

  • 堆排序:

    • 使用上面构造的最大堆,将堆顶和堆底元素交换,

    • 这个时候利用上面第3点性质 将heap-size[A] -1,但数组length[A]不变,堆中存在的最大元素被分离出堆。

    • 这时候又不是最大堆了,故 :对 整个堆构造最大堆。

    • 使用递归 重复上面 2,3 步骤

      对最大堆进行堆排序

具体实现如下:

import java.util.Arrays;

/**
 * 堆排序
 * 1. 构建最大堆   build_max_heap
 * 2. 对最大堆排序 heapSort
 */
public class heap_sort {
    /**
     * 交换值
     * @param arr
     * @param a 元素的下标
     * @param b 元素的下标
     */
    public static void swap(int[] arr, int a, int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    /**
     * 对每一个 子堆进行 最大堆调整
     * PARENT floor(i/2)  LEFT 2*i + 1  RIGHT 2*i + 2
     */
    public static void max_heapify(int[] arr, int len, int index){
        int parent_node = (int) Math.floor(index/2);
        int left_node = 2 * index + 1;
        int right_node = 2 * index + 2;
        int largest = index;

        if(left_node < len && arr[left_node] > arr[index]){
            largest = left_node;
        }
        if(right_node < len && arr[right_node] > arr[largest]){
            largest = right_node;
        }
        if(largest != index){
            swap(arr, largest, index);
            //调整交换后的子堆
            max_heapify(arr, len, largest);
        }
    }

    /**
     * 对整个数组进行 最大堆调整, 逻辑结构为二叉堆
     * len/2 -1 找出最后一个有子节点的子堆
     */
    public static void build_max_heap(int[] arr, int len){
        if(len<=1){
            System.out.println("");
        }
        // 从最后一个自下而上进行最大堆构建
        for(int i = len/2 -1; i >= 0; i--){
            max_heapify(arr, len, i);
        }

        System.out.println("最大堆:" + Arrays.toString(arr));
    }

    /**
     * 根据最大堆 进行堆排序, 输出排好序的数组
     * len -1 数组和逻辑二叉树 最后一个节点  最后一个节点和最大根节点交换
     */
    public static int[] heapSort(int[] arr, int len){
        if(len<=1){
            return arr;
        }
        build_max_heap(arr, len);
        // 从最后一个节点利用 堆的性质排序
        for(int i = len -1; i >= 1; --i){
            swap(arr, 0, i);
            max_heapify(arr, --len, 0);
        }

        // 排序后数组
        System.out.println("排序后:" + Arrays.toString(arr));
        return arr;
    }

    public static void main(String[] args) {
        int[] arr = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
        int len = arr.length;
        heapSort(arr, len);
    }
}




时间复杂度:

= 初始化最大堆阶段 + 调整堆的阶段

具体计算过程参考

  • 初始化最大堆阶段

    O(n) 调用一次

  • 调整堆的阶段 时间复杂度
    调整堆的时间复杂度是lgn,调用了n-1次 O(nlgn)

    O(n)+O(nlgn)≈ O(nlgn)
    故:整体的时间复杂度约为: O(nlgn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值