堆和堆排序

大家好,我是周一。

今天我们聊聊堆,以及堆排序。

一、堆

谈到堆,首先我们要从二叉树说起,从二叉树到完全二叉树,再才到堆。

1、二叉树

每个结点最多只能有两棵子树,且有左右之分

image.png

2、完全二叉树

一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中(除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树)编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

image.png

通俗的说法就是:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。

对于某个位置为i的节点,左孩子(如果有)2i+1,右孩子(如果有)2i+2,父节点(i-1)/2(向下取整)

3、堆

首先是一个完全二叉树。同时区分大根堆和小根堆。

大根堆:每一颗子树的最大值都是头节点。

小根堆:每一颗子树的最小值都是头节点。

image.png

(1)对于用户依次输入数字的一个数组,如何将其构造为大根堆?

每插入一个数都和父节点比较,大于父节点则和父节点交换,直到根节点;如果小于父节点,则马上停止比较。

// 对于新加进来的i位置的数,请放到数组合适位置,使其成为一个大根堆
private void heapInsert(int[] arr, int i) {
    // i = 0 或 i位置数小于父节点
    // while包含这两种终止条件
    while(arr[i] > arr[(i - 1)/2]) {
        swap(arr, i, (i - 1)/2);
        i = (i - 1)/2;
    }
}

(2)获取当前数组最大值,并从堆中删除,同时维持大根堆结构

public int pop() {
    int ans = heap[0];
    swap(heap, 0, --heapSize);
    heapify(heap, 0, heapSize);
    return ans;
}
// 将index位置的数往下沉,直到较大的孩子都没自己大,或者没孩子了
private void heapify(int[] arr, int index, int heapSize) {
    // 左子树位置
    int left = 2 * index + 1;
    while(left < heapSize) {        
        // 找到左右子树哪个值更大
       int maxIndex = (left + 1) < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
       // 将左右子树中较大的和父节点比较
       maxIndex = arr[maxIndex] > arr[index] ? maxIndex : index;
       // 左右子树较大的值都小于父节点,停止循环     
       if (maxIndex == index) {
           break;
       }
       // 将左右子树中较大的和父节点交换
       swap(arr, maxIndex, index);
       // 当前比较的节点位置来到较大的子节点
       index = maxIndex;
       // 重新获取当前节点的左子树
       left = 2 * index + 1;
    }
}

4、PriorityQueue

底层就是用堆实现的,默认是小根堆

5、时间复杂度

heapinsert,在已经是堆结构的数中加入一个数,时间复杂度是O(logN)

heapify,在已经是堆结构的数中加入一个数,时间复杂度是O(logN)

二、堆排序

1、将整个数组调整为大根堆,那么此时0位置的数就是最大值

2、将0位置和数组最大位置的数交换,此时的最大值就处于最后排好序的位置了

3、数组的调整范围个数减一,循环执行1、2步,直到数组为空

public static void heapSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    // 将整个数组调整为大根堆,O(NlogN)
    for (int i = 0; i < arr.length; i++) { // O(N)
        heapinsert(arr, i); // O(logN)
    }
    // 获取当前数组大小
    int heapSize = arr.length;
    // 0位置和数组最大位置的数交换
    swap(arr, 0, --heapSize);
    while(heapSize > 0) {
        // 将交换到0位置的数下沉,即将当前数组再次调整为大根堆
        heapify(arr, 0, heapSize);
        // 0位置和数组最大位置的数交换
        swap(arr, 0, --heapSize);
    }    
}

4、复杂度

(1)时间复杂度:O(N*logN)

(2)额外空间复杂度:O(1)

你好,周一

6 声望
1 粉丝
0 条评论
推荐阅读
动态规划
前一篇我们仔细聊了聊递归,如果大伙都仔细敲了相关代码,没有收获那是不可能的。同时敏锐的伙伴可能也发现了,接下来我们要说啥了——动态规划,对,终于终于我们聊到动态规划了。

周一pro阅读 713

PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go10阅读 1.9k评论 2

封面图
万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide5阅读 789

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬8阅读 1.1k

与RabbitMQ有关的一些知识
工作中用过一段时间的Kafka,不过主要还是RabbitMQ用的多一些。今天主要来讲讲与RabbitMQ相关的一些知识。一些基本概念,以及实际使用场景及一些注意事项。

lpe2348阅读 1.9k

封面图
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 2.3k评论 2

封面图
Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...

码哥字节6阅读 1.5k

封面图

你好,周一

6 声望
1 粉丝
宣传栏