数据结构与算法之堆

数据结构与算法之堆

前言

最近好久没更新了,一方面是手头活有点多了,有些忙了,业务代码得走起了,另外一方面,生活上总有些小事情打断了节奏。总结下来,对,就是我太懒了。我认可,我不配,呜呜呜。 在这里插入图片描述java

因此我来更新了。。。。。。
本期主讲的数据结构,他与栈常常成双入对,他就是堆,栈的好兄弟。node

定义

老规矩,先给出堆的定义,如下是维基百科对堆的定义算法

In computer science, a heap is a specialized tree-based data structure which is essentially an almost complete tree that satisfies the heap property: in a max heap, for any given node C, if P is a parent node of C, then the key (the value) of P is greater than or equal to the key of C. In a min heap, the key of P is less than or equal to the key of C. The node at the "top" of the heap (with no parents) is called the root node.数组

基本就是,在彻底二叉树的基础上,再加上一些特殊的性质。markdown

性质

  1. 彻底二叉树数据结构

    堆首先必须是一个彻底二叉树,这个时候你确定想问小泉,彻底二叉树是啥子。
    待小泉画个图给你们xue微讲解一下。 彻底二叉树less

在图中,从根节点开始从上至下而后每层从左到右进行对节点进行标记序号,若是每一个节点的序号与满二叉树(每层节点都达到最大个数)状态下的序号一致,则认为是彻底二叉树。
通俗来讲,对于一个彻底二叉树, 每一层节点需全不为空时,才能够有子节点,且必须先有左子节点再有右子节点。
看图其实也能够了解到,满二叉树其实也是一种彻底二叉树。oop

  1. 节点值比子节点大/小网站

    最大堆:每个节点值都要比其左右子节点值大 最大堆spa

    最小堆:每个节点值都要比其左右子节点值小 最小堆

堆的构建

理论来讲,堆的构建能够理解为节点的插入或者节点的下沉:
插入即新的节点插入到老节点的子节点进行上浮操做。
下沉即新节点每次从堆顶“插入”,进而每次都须要从堆顶进行下沉操做。
实际上,若是给出的是数组,咱们能够把所给数组看成彻底二叉树:
从叶子节点的父节点往“堆顶”进行下沉操做。
若是你曾经看过Java优先队列(PriorityQueue,默认为小顶堆)的源码,你会发现,它的内部其实有着一个数组,初始容量为11。 在这里插入图片描述 在进行堆的一系列操做以前,先预热下,在使用堆时两个很重要很底层的操做:下沉和上浮。

下沉

下沉又称为堆化,当发现节点元素比子节点大(最小堆)或者比子节点小(最大堆)时,将节点值与较大子节点(最小堆)或者较小子节点(最大堆)的值相交换,即为沉操做。
用途:(1) 删除堆顶元素后从新造成堆 (2) 建立堆

void siftDown(int index) {
        if (index == size) return;
        int childIndex = 2 * index + 1;
        int tmp = heap[index];
        while(childIndex <= size) {
            if (childIndex + 1 <= size && heap[childIndex] < heap[childIndex + 1]) {
                childIndex++;
            }
            if (heap[childIndex]<= tmp) break;
            heap[index] = heap[childIndex];
            index = childIndex;
            childIndex = 2 * index + 1;
        }
        heap[index] = tmp;
    }
复制代码

上浮

当发现节点元素比父节点小(最小堆)或者比父节点大(最大堆)时,将节点值与父节点值相交换,即为上浮操做。
用途:堆的插入

void siftUp(int index) {
        if (index ==1) return;
        int parentIndex = index/2;
        int tmp = heap[index];
        while (parentIndex >=1 && heap[parentIndex] < heap[index]) {
            heap[index] = heap[parentIndex];
            index = parentIndex;
            parentIndex = index / 2;
        }
        heap[index] = tmp;
    }
复制代码

插入

对于插入的操做过程。谨记一点,先按照彻底二叉树的形式将新节点看成叶子节点插入。而后再对该节点进行上浮操做。
具体事例以下:
向已经符合堆结构的[12, 10,9, 5, 6, 8]中插入13

  1. 先将13按照子节点插入到9的右子节点处,符合彻底二叉树的性质。
  2. 对13进行上浮操做,与9作比较,最大堆的第二条性质,节点要比子节点大,所以与9互换,继续上浮。
  3. 重复2的操做直至没法上浮(没法上浮两种可能,一是达到堆顶,二是不知足上浮的条件)

过程图剖解以下 堆的插入

删除

对于删除的操做过程。谨记一点,查找到最后一个元素,将要删除的节点值与最后一个节点值互换,剔除最后一个节点。而后再对互换后的节点进行下沉操做。
具体事例以下:
在已经符合堆结构的[12, 10,9, 5, 6, 8]中删除12

  1. 先将12与8互换,再删除掉最后一个节点
  2. 对8进行下沉操做,与9,10作比较,最大堆的第二条性质,节点要比子节点大,所以与较大的子节点10互换,继续下沉。
  3. 重复2的操做直至没法下沉

过程图剖解以下 堆的删除

复杂度分析

操做 时间复杂度 时间复杂度
堆的建立 O(N) O(N)
堆的插入 O(logN) O(logN)
删除堆顶元素 O(logN) O(logN)
其实堆的主要操做也就是这三项,插入堆,删除堆顶元素\
堆这一数据结构相对来讲,其实没有那么复杂但倒是一些特定问题的好帮手——排序问题、前K个元素、第K个元素等等此类的问题。

应用

堆排序

因为堆的性质,最大堆的堆顶必定是最大值,最小堆的堆顶必定是最小值,所以删除堆顶后新堆顶是次大(或小)值,以此类推。

public class HeapSort {
    // 堆排序
    public static int[] heapSort(int[] nums) {
        int n = nums.length;
        int i,tmp;
        //构建大顶堆
        for(i=(n-2)/2;i>=0;i--) {//从只有一层子节点的父节点开始往树的根节点进行下沉操做
            shiftDown(nums,i,n-1);
        }
        //进行堆排序,删除堆顶,进行堆重构后堆顶依然是最大的
        for(i=n-1;i>=1;i--){
            //删除堆顶的过程是将最后一个节点值替换堆顶值,而后删除最后一个节点,其实也就是与最后一个节点互换
            tmp = nums[i];
            nums[i] = nums[0];
            nums[0] = tmp;
            shiftDown(nums,0,i-1);
        }
        return nums;
    }

    //小元素下沉操做
    public static void shiftDown(int[] nums, int parentIndex, int n) {
        //临时保存要下沉的元素
        int temp = nums[parentIndex];
        //左子节点的位置
        int childIndex = 2 * parentIndex + 1;
        while (childIndex <= n) {
            // 若是右子节点比左子节点大,则与右子节点交换
            if(childIndex + 1 <= n && nums[childIndex] < nums[childIndex + 1])
                childIndex++;
            if (nums[childIndex] <= temp ) break;//该子节点符合大顶堆特色
            //注意因为咱们是从高度为1的节点进行堆排序的,因此不用担忧节点子节点的子节点不符合堆特色
            // 父节点进行下沉
            nums[parentIndex] = nums[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * parentIndex + 1;
        }
        nums[parentIndex] = temp;
    }

    public static void main(String[] args) {
        int[] a = {91,60,96,13,35,65,81,46,13,10,30,20,31,77,81,22};
        System.out.print("排序前数组a:\n");
        for(int i:a) {
            System.out.print(i);
            System.out.print(" ");
        }
        a=heapSort(a);
        System.out.print("\n排序后数组a:\n");
        for(int i:a) {
            System.out.print(i);
            System.out.print(" ");
        }
    }
}

复制代码

Top K问题

Top K 问题是一类题的统称,主要是想要选取知足某一条件前K个最大化知足条件的元素。

题目

Leetcode 347. 前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。\

示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]

解析

将全部元素加入到小顶堆中,若是超过了K个元素,那么每多一个比堆顶更加知足条件的元素就删除堆顶,而后插入元素。

代码

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        // int[] 的第一个元素表明数组的值,第二个元素表明了该值出现的次数
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] pre, int[] nex) {
                return pre[1] - nex[1];
            }
        });
        for (int key : map.keySet()) {
            int value = map.get(key);
            if (queue.size() == k) {
                if (queue.peek()[1] < value) {
                    queue.poll();
                    queue.add(new int[]{key, value});
                }
            } else {
                queue.add(new int[]{key, value});
            }
        }
        int[] kMax = new int[k];
        for (int i = 0; i < k; ++i) {
                kMax[i] = queue.poll()[0];
        }
        return kMax;
    }
}
复制代码

总结

堆这一数据结构相对红黑树、B+树等结构要相对简单不少,掌握堆的两个性质、下沉和上浮的操做,构建起堆并非什么难事,固然堆的构建过程了解了以后,更多的是如何去使用这一数据结构,Java当中就有集合类PriorityQueue(优先队列)做为堆提供给咱们使用,之后带你们一块儿看一看它的源码吧~
今天的分享就到这里,但愿对您有所帮助。
目前在建设本身的网站,有兴趣也可关注下:小泉的开发修炼之路

相关文章
相关标签/搜索