ch8 - Heap堆

支持的操作:O(logn) Add / O(logn) Remove / O(1) Min or Max (n是堆中的元素的個數)
Min Heap/ Max Heap(不能同時求最大和最小)

目錄:

  • 1.基礎知識 - 取最大/小值,插入,刪除
  • 2.Heap的基本原理和具體實現
    2.1 基本原理
    2.2 具體實現
    2.3 基本操作 - Heapify
  • 3.相關題目
    3.1 ugly-number-ii 醜數(4 in lintcode)
    3.2 top-k-largest-numbers 前k大的數(544 in lintcode)
    3.3 top-k-largest-numbers-ii 前k大的數(545 in lintcode)
    3.4 merge-k-sorted-lists 合併k個排序鏈表(104 in lintcode)
    3.5 kth-largest-element-ii 第k大的數(606 in lintcode)
    3.6 其他題目

1. 基礎知識

1.1結構 - 儘量均衡 在這裏插入圖片描述

1.2 樹高

n個節點,heap的樹高是logn。 但不能認爲二叉樹的樹高是logn

1.3 節點順序

MaxHeap:- 1.每層都滿的二叉樹;2.每個小三角,parent的value一定大於等於左右兒子的value。
在這裏插入圖片描述
注:不能確定左右子樹中的value值得大小,也就是說parent的值比左右兒子大,但左右孩子的大小不確定。

1.4 操作1: 取最大值最小值在heap[0],複雜度爲O(1)

1.5 操作2: 插入Add - 如下圖,小頂堆123插入0

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

1.6 操作3: 刪除 Remove - 如下 刪除

a. 用最底層最右邊的節點換掉堆頂的元素
b. 當父節點比左右孩子節點都大時,應該換左右孩子中較小的那個節點上來
c. 刪除非堆頂元素時,將堆尾的元素替換掉要刪除的元素
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

1.8 java中的優先隊列Priority Queue 和Heap是等價的。

支持:offer() \ pop() \ peek()
不支持: 取第k大。

Priority Queue 只有刪除堆頂元素時,時間複雜度是O(logn); 用pop(any)刪除任意元素時,時間複雜度是:O(n).

如果要用堆頂替換要刪除的元素,只能自己實現,或者用HashHeap。

java中有TreeMap來支持刪除任意元素,c++中是Set。**TreeMap是一棵 Balance的BST,是一棵紅黑樹,**並且可以同時知道最大值最小值

在這裏插入圖片描述

1.9 TreeMap & PQ在這裏插入圖片描述

2. Heap的基本原理和具體實現

2.1 基本原理

2.2 具體實現

2.3 基本操作 - Heapify

3. 相關題目

3.1 ugly-number-ii 醜數(4 in lintcode)

1.題目

http://www.lintcode.com/zh-cn/problem/ugly-number-ii/
http://www.jiuzhang.com/solution/ugly-number-ii/

設計一個算法,找出只含素因子2,3,5 的第 n 小的數。
符合條件的數如:1, 2, 3, 4, 5, 6, 8, 9, 10, 12…

2.思路 & 代碼

O(n)的算法不好理解,一定要掌握下面這種O(nlogn)的方法
在這裏插入圖片描述
思路:從{1}開始,每次取最小的數拿出來,依次*2,3,5,將新得到的數重新放進去。取最小是爲了避免遺漏。第i輪一定拿到的是第i小的數。即每一輪,都取當前最小的數,很顯然,使用堆。還需要去重,因爲10=25=52,所以還需要hash表。

PriorityQueue + Hash表

c++優先隊列的使用https://www.cnblogs.com/cielosun/p/5654595.html

代碼:

class Solution {
public:
    /*
     * @param n: An integer
     * @return: the nth prime number as description.
     */
    typedef long long LL;//定義long long 類型的別名    
    const int coeff[3]={2,3,5};//定義基礎數組
    int nthUglyNumber(int n) {
        priority_queue<LL,vector<LL>,greater<LL>> pq; //定義優先隊列這個是越小的整數優先級越大的優先隊列
        set<LL> s;//定義集合 排重
        pq.push(1);//進入隊列
        s.insert(1);//插入集合

        LL x;
        for(int i=1;i<=n;i++){
            x=pq.top();//獲取到隊列第一個元素(因爲是按最小優先的原則排序的)
            pq.pop();//第一元素出隊列 所以 第1500個醜數就是pop1499後的那個元素
            for(int j=0;j<3;j++){
                LL x2=x*coeff[j];//x2是2 3 5的倍數
                //判斷集合中有沒有x2 若沒有進入if中
                if(!s.count(x2)){
                    s.insert(x2);//插入到s集合中
                    pq.push(x2);//進隊列
                }
            }
        }
        return x;
    }
};

O(n)的解法:

class Solution {
public:
    /*
     * @param n: An integer
     * @return: the nth prime number as description.
     */
    int nthUglyNumber(int n) {
        // write your code here
        int* ugly = new int[n];
        ugly[0] = 1;
        int next = 1;
        int *p2 = ugly;
        int *p3 = ugly;
        int *p5 = ugly;
        while(next < n){
            int m = min(min(*p2 *2, *p3*3), *p5 * 5);
            ugly[next] = m;
            while(*p2 * 2 <= ugly[next]){
                p2++; // *p2++
            }
            while(*p3 * 3 <= ugly[next]){
                p3++; // *p3++
            }
            while(*p5 * 5 <= ugly[next]){
                p5++; // *p5++
            }
            next++;
        }

        int res = ugly[n-1];
        delete [] ugly;
        return res;
    }
};

**p++、(p)++、++p、++p 的區別

int a[5]={1,2,3,4,5};
int *p = a;

*p++ 先取指針p指向的值(數組第一個元素1),再將指針p自增1;

        cout << *p++; //  結果爲 1

        cout <<(*p++);  // 1

(*p)++ 先去指針p指向的值(數組第一個元素1),再將該值自增1(數組第一個元素變爲2
        cout << (*p)++; //  1
        cout <<((*p)++)  //2
*++p   先將指針p自增1(此時指向數組第二個元素),* 操作再取出該值

        cout << *++p; //  2
        cout <<(*++p)  //2

++*p  先取指針p指向的值(數組第一個元素1),再將該值自增1(數組第一個元素變爲2)
      cout <<++*p; //   2   
        cout <<(++*p)  //2

注意,上面的每條cout輸出,要單獨輸出才能得到後面的結果。

3.2 top-k-largest-numbers 前k大的數(544 in lintcode)

1.題目

http://www.lintcode.com/zh-cn/problem/top-k-largest-numbers/
http://www.jiuzhang.com/solution/top-k-largest-numbers/
在一個數組中找到前K大的數

樣例
給出 [3,10,1000,-99,4,100], k = 3.
返回 [1000, 100, 10]

2.思路 & 代碼

3.3 top-k-largest-numbers-ii 前k大的數(545 in lintcode)

1.題目

http://www.lintcode.com/zh-cn/problem/top-k-largest-numbers-ii/
http://www.jiuzhang.com/solution/top-k-largest-numbers-ii/
實現一個數據結構,提供下面兩個接口
1.add(number) 添加一個元素
2.topk() 返回前K大的數

樣例

s = new Solution(3);
>> create a new data structure.
s.add(3)
s.add(10)
s.topk()
>> return [10, 3]
s.add(1000)
s.add(-99)
s.topk()
>> return [1000, 10, 3]
s.add(4)
s.topk()
>> return [1000, 10, 4]
s.add(100)
s.topk()
>> return [1000, 100, 10]

2.思路 & 代碼

用MinHeap。維護大小爲k的小頂堆,想擠進前k,只需要擠掉第k名即可。

3.4 merge-k-sorted-lists 合併k個排序鏈表(104 in lintcode)

1.題目

http://www.lintcode.com/zh-cn/problem/merge-k-sorted-lists/
合併k個排序鏈表,並且返回合併後的排序鏈表。嘗試分析和描述其複雜度。

樣例
給出3個排序鏈表[2->4->null,null,-1->null],返回 -1->2->4->null
在這裏插入圖片描述

2.思路 & 代碼

一般這種題目有兩種做法。

第一種做法比較容易想到,就是有點類似於MergeSort的思路,就是分治法。先把k個list分成兩半,然後繼續劃分,知道剩下兩個list就合併起來,合併時會用到類似 Merge Two Sorted Lists 這道題的思路。 這種思路我們分析一下複雜度,如果有k個list,每個list最大長度是n,那麼我們就有分治思路的複雜度計算公式 T(k) = 2T(k/2)+O(n*k)。 其中T(k)表示k個list合併的時間複雜度,用主定理可以算出時間複雜度是O(nklogk)。分治法又分兩種實現方式,一種是遞歸,另一種是兩兩鏈表進行多輪合併。

分治法java可以過,c++超時。

第二種做法是運用堆,也就是我們所說的priority queue。我們可以考慮維護一個大小爲k的堆,先把每個list的第一個元素放入堆之中,然後每次從堆頂選取最小元素放入結果最後的list裏面,然後讀取該元素所在list的下一個元素放入堆中,重新維護好堆,然後重複這個過程。因爲每個鏈表是有序的,每次又是取當前k個元素中最小的,所以最後結果的list的元素是按從小到大順序排列的。這種方法的時間複雜度也是O(nklogk)。

/**
 * Definition of ListNode
 * class ListNode {
 * public:
 *     int val;
 *     ListNode *next;
 *     ListNode(int val) {
 *         this->val = val;
 *         this->next = NULL;
 *     }
 * }
 */
class Solution {
public:
    /**
     * @param lists: a list of ListNode
     * @return: The head of one sorted list.
     */
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        // write your code here
        if(lists.size()==0){
            return NULL;
        }

        while(lists.size()>1){
            vector<ListNode*> new_lists;
            for(int i=0;i+1<lists.size();++i){
                ListNode* tmp = mergeTwoLists(lists[i], lists[i+1]);
                new_lists.push_back(tmp);
            }
            if(lists.size()%2 == 1){
                new_lists.push_back(lists[lists.size()-1]);
            }

            lists = new_lists;
        }

        return lists[0];
    }

    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){
        ListNode dummy(0);
        ListNode* tail = &dummy;
        while(l1 && l2){
            if(l1->val < l2->val){
                tail->next = l1;
                l1 = l1->next;
            }
            else{
                tail->next = l2;
                l2 = l2->next;
            }
            tail = tail->next;
        }
        if(l1){
            tail->next = l1;
        }
        if(l2){
            tail->next = l2;
        }
        return dummy.next;
    }
};
/**
 * Definition of ListNode
 * class ListNode {
 * public:
 *     int val;
 *     ListNode *next;
 *     ListNode(int val) {
 *         this->val = val;
 *         this->next = NULL;
 *     }
 * }
 */

struct compare{
    bool operator()(const ListNode* A, const ListNode* B)const{
        return A->val > B->val;
    }
};

class Solution {
public:
    /**
     * @param lists: a list of ListNode
     * @return: The head of one sorted list.
     */
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        // write your code here
        if(lists.size()==0){
            return NULL;
        }

        priority_queue<ListNode*, vector<ListNode*>, compare> q;
        for(int i=0;i<lists.size();++i){
            if(lists[i])
               q.push(lists[i]);
        }

        ListNode dummy(0);
        ListNode* tail = &dummy;
        while(!q.empty()){
            ListNode* head = q.top();
            q.pop();
            tail->next = head;
            tail = tail->next;
            if(head->next){
                q.push(head->next);
            }
        }

        return dummy.next;
    }
};

3.5 kth-largest-element-ii 第k大的數(606 in lintcode)

1.題目

http://www.lintcode.com/zh-cn/problem/merge-k-sorted-lists/

2.思路 & 代碼

使用heap和quick-select的對比:
在這裏插入圖片描述

struct compare{
    bool operator()(const int &a, const int &b)const{
        return a > b;
    }
};

class Solution {
public:
    /*
     * @param nums: an integer unsorted array
     * @param k: an integer from 1 to n
     * @return: the kth largest element
     */
    int kthLargestElement2(vector<int> &nums, int k) {
        //write your code here
        priority_queue<int, vector<int>, compare> pq;
        for(int i=0;i<k;++i){
            pq.push(nums[i]);
        }

        for(int i=k;i<nums.size();++i){
            if(nums[i] > pq.top()){
                pq.pop();
                pq.push(nums[i]);
            }
        }

        return pq.top();
    }
};

3.6 其他題目

http://www.lintcode.com/en/problem/high-five/(A)
http://www.lintcode.com/en/problem/k-closest-points/(L, A, F)
http://www.lintcode.com/problem/merge-k-sorted-arrays/
http://www.lintcode.com/problem/data-stream-median/
http://www.lintcode.com/problem/top-k-largest-numbers/
http://www.lintcode.com/problem/kth-smallest-number-in-sorted-matrix/

Hive Five 優秀成績 - heap、amazon

http://www.lintcode.com/en/problem/high-five/(A)

如果用堆,建堆時間 O(5log5), 後續調整堆的總時間:O(nlog5)

不用堆,只是用一個數組,則總時間是:O(n * 5)

時間在同一數量級的情況下,採用後者,代碼更簡單。

Top k largest numbers 前k大的數 - heap、priority_queue

http://www.lintcode.com/problem/top-k-largest-numbers/

兩種思路:

思路1:quickSort的思路,只進行quiksort的前k輪

思路2:使用小頂堆

struct compare{
    bool operator()(const int &a, const int &b)const{
        return a > b;
    }  
};

class Solution {
public:
    /*
     * @param nums: an integer array
     * @param k: An integer
     * @return: the top k largest numbers in array
     */
    vector<int> topk(vector<int> nums, int k) {
        // write your code here
        priority_queue<int, vector<int>, compare> pq;
        for(int i=0;i<k;++i){
            pq.push(nums[i]);
        }

        for(int i=k;i<nums.size();++i){
            if(nums[i] > pq.top()){
                pq.pop();
                pq.push(nums[i]);
            }
        }

        vector<int> res;
        while(!pq.empty()){
            res.push_back(pq.top());
            pq.pop();
        }
        reverse(res.begin(),res.end());

        return res;
    }
};
class Solution {
public:
    /*
     * @param nums: an integer array
     * @param k: An integer
     * @return: the top k largest numbers in array
     */
    vector<int> topk(vector<int> nums, int k) {
        // write your code here
        if(nums.size() == 0){
            return nums;
        }

        quicksort(nums, 0, nums.size()-1, k);

        vector<int> res(k,0);
        for(int i=0;i<k;++i){
            res[i] = nums[i];
        }

        return res;
    }

    void quicksort(vector<int> &nums, int start, int end, int k){
        if(start >= k){
            return;
        }
        if(start >= end){
            return;
        }

        int left = start, right = end, pivot = nums[(left+right)/2];
        while(left <= right){
            while(left<=right && nums[left] > pivot){
                left++;
            }
            while(left<=right && nums[right] < pivot){
                right--;
            }
            if(left <= right){
                int tmp = nums[left];
                nums[left] = nums[right];
                nums[right] = tmp;
                left++;
                right--;
            }
        }

        quicksort(nums, start, right, k);
        quicksort(nums, left, end, k);
    }
};

K Closest Points K個最近的點 - heap、 amazon、 linkedin

http://www.lintcode.com/en/problem/k-closest-points/

大頂堆,每次去掉最大的,始終維持最小的五個

關於c++中優先隊列的使用

struct compare{
    bool operator()(const Point& a, const Point& b)const{
        int diff = getDis(a,global_origin) - getDis(b, global_origin);
        if(!diff){
            diff = a.x-b.x;
        }
        if(!diff){
            diff = a.y - b.y;
        }
        return diff<0;
    } 
};

priority_queue<Point, vector, compare> pq; //該優先隊列按從大到小排列

compare函數的定義中,如果return的地方是小於號,則是按從大到小排列,如果是>,則是按從小到大排列。默認情況下是從大到小排列。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章