java数据结构与算法基础-----排序------堆排序

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846

文章目录

  1. 堆排序是利用堆(数据结构)设计的排序算法,属于选择排序,最坏,最好,平均时间复杂度均为O(n logn),不稳定排序
  2. 堆是具有以下性质的完全二叉树:
  1. 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
  2. 不对结点的左孩子的值和右孩子的值的大小关系进行要求。
  3. 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
    在这里插入图片描述
    在这里插入图片描述
堆排序基本思想(以使用小顶堆为例)
  1. 堆排序,就是符合一定规则的完全二叉树,这个规则是:整个二叉树中,你随便挑出一颗子树,都满足根结点>=左右孩子(大根堆)或者根结点<=左右孩子(小根堆)
  2. 所以如果我们采用从下往上构建堆,是比较方便的。只需要关注当前的子树满足指定规则即可
  3. 例如我们想要构建一个长度为4的小顶堆,需要插入的元素是[3,2,3,1,2,4,5,5,6],我们想要实现,保留[3,2,3,1,2,4,5,5,6]中最大的4个在小顶堆中
  1. 先无脑插入4个结点,因为我们的堆长度为4,heap=[3,2,3,1]
  2. 然后调整堆,从第一个非叶子结点开始调整,利用完全二叉树公式len/2-1是第一个非叶子,len/2-2是第二个非叶子,依次类推
  1. 第一个非叶子结点root为4/2 - 1 = 1,也就是heap[1] = 2. 然后根据完全二叉树公式获取其左右孩子,left = root * 2+1 和 right = root * 2+2。也就是left = 1 * 2+1 = heap[3] = 1.right = 1 * 2+2 = 4,超出堆大小,没有右孩子。
  2. 此时我将让root和left以及right比较,谁小谁做根结点。发现root = heap[1] = 2 > left = heap[3] = 1.故root下降到left位置,left上升到root位置。
  3. 从而堆变成heap = [3,1,3,2]
  4. 第二个非叶子结点root为4/2-2 = 0,也就是heap[0] = 3, 其left = 0 * 2 +1 = heap[1] = 1.其right = 0 * 2+2 = heap[2] = 3.
  5. 比较后发现,root>left,因此进行root下降到left操作,也就是交换位置swap(root,left),此时heap = [1,3,3,2]
  6. 然后发现此时root指向原来left位置,也就是下降一次后,root = heap[1] = 3,我们发现它还有一个左孩子left = 1 * 2+1 = heap[3] = 2. 我们发现root>left,因此root继续下降,此时heap = [1,2,3,3]
  1. 堆构建完成后,我们进行插入操作,现在该插入第5个结点,[2],我们发现[2]>堆顶的[1].因此[1]肯定不是序列中最大的4个中的一个,所以我们将堆顶元素heap[0] 换为 [2].然后重新回到上面的调整堆的操作。也就是不断下降的操作,直到[2]下降到符合小根堆的位置。
  2. 依次类推,直到所有元素处理完成,就构建完成了一个小顶堆
代码
  1. 用到的公式(二叉树的基本公式)
  1. arr[i]<=arr[2 * i+1] && arr[i] <= arr[2 * i+2] 小顶堆条件,当前非叶子节点arr[i],左节点和右节点,都小于它,大顶堆正好相反,左右都大于它本身
  2. 第一个非叶子节点arr.length/2-1
  3. 当前节点的左子节点,i * 2+1,当前节点右子节点i * 2+2

直接实现小根堆难免让人不知道这是干什么用的,因此,用一道算法题来理解。这道题的代码完全就是小根堆的代码。

🏆LeetCode215. 数组中的第K个最大元素https://blog.csdn.net/grd_java/article/details/136932454
class Solution {
	//通过小根堆,获取nums中最大的k个值,并返回这4个值中最小的那个
    public int findKthLargest(int[] nums, int k) {
        int[] minHeap = new int[k];//小根堆
        for (int i = 0; i < k; i++) {//大小为k
            minHeap[i] = nums[i];
        }
        //k/2-1是二叉树的知识,代表以k个结点构成的二叉树的第一个非叶子结点。k/2-2是第二个非叶子,以此类推。i == 0是整个二叉树的根结点
        for (int i = k / 2 - 1; i >= 0; i--) {//调整小根堆,从下到上,依次让每一颗子树满足小根堆
            adjustHeap(minHeap, i);//i是二叉树的每个非叶子结点,小根堆的要求是:每个子树,根结点都是整棵树最小
        }
        //小根堆构建完成后,minHeap[0]就是当前第k大的数。接下来需要不断进行判断和入堆操作
        for (int i = k; i < nums.length; i++) {
            if (nums[i] > minHeap[0]) {//如果当前i,是比小根堆堆顶更大的元素,那么堆顶不是第k大,
                minHeap[0] = nums[i];//将堆顶出堆,并将i放在堆顶位置
                adjustHeap(minHeap, 0);//此时很有可能小根堆逻辑被破坏,也就是i太大,不满足小根堆,因此需要让i进行下降调整,让其重新满足小根堆定义
            }
        }
        return minHeap[0];
    }

    /**
     * 以root为根结点构建/调整堆
     * @param array 堆
     * @param root 当前根结点
     */
    private void adjustHeap(int[] array, int root) {
        //让root结点下降到合适位置,以满足小根堆效果(任何一颗子树,根结点都是最小的)
        while (true) {//当堆调整完成后,结束
            int left = 2 * root + 1;//获得root的左子结点下标
            int right = left + 1;//获得root的右子结点下标
            int min = root;//最小值,最终需要放到root结点位置
            //如果左子结点存在,并且左子结点更小,让min指向这个结点
            if (left < array.length && array[left] < array[min]) min = left;
            //如果右子结点存在,并且右子结点更小,让min指向这个结点
            if (right < array.length && array[right] < array[min]) min = right;
            //如果min == root说明小根堆调整结束
            if (min == root) break;
            //让min当前指向位置和root交换,也就是下降操作,说明root当前指向的结点不是最小值,不满足小根堆
            //因为小根堆,越上面层次的结点,越小,所以如果当前root太大,需要让其下降
            swap(array, root, min);
            //root本次下降完成后,min的位置是root新的位置。因为root下降到min的位置
            //让root指向min,然后继续循环,判断是否root需要继续下降。直到它下降到合适位置
            root = min;
        }
    }

    private void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}
  • 39
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殷丿grd_志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值