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的地方是小于号,则是按从大到小排列,如果是>,则是按从小到大排列。默认情况下是从大到小排列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值