支持的操作: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结构 - 尽量均衡 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190116203313877.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzU1MTIyOQ==,size_16,color_FFFFFF,t_70)
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![在这里插入图片描述](https://img-blog.csdnimg.cn/20190116203724488.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzU1MTIyOQ==,size_16,color_FFFFFF,t_70)
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的地方是小于号,则是按从大到小排列,如果是>,则是按从小到大排列。默认情况下是从大到小排列。