數據結構與算法之堆

數據結構與算法之堆

前言

最近好久沒更新了,一方面是手頭活有點多了,有些忙了,業務代碼得走起了,另外一方面,生活上總有些小事情打斷了節奏。總結下來,對,就是我太懶了。我認可,我不配,嗚嗚嗚。 在這裏插入圖片描述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(優先隊列)做爲堆提供給咱們使用,之後帶你們一塊兒看一看它的源碼吧~
今天的分享就到這裏,但願對您有所幫助。
目前在建設本身的網站,有興趣也可關注下:小泉的開發修煉之路

相關文章
相關標籤/搜索