堆通常是一个可以被看做一棵完全二叉树的数组对象。
堆总是满足下列性质:
- 堆中某个结点的值总是不大于或不小于其父结点的值;
- 堆总是一棵完全二叉树。
建堆
- 从上往下
假设给定一个数组,从第一个元素依次插入在堆中,调整至满足堆的结构(大根堆)
流程:比较当前位置与父结点位置,如果当前位置比父节点大,则交换。
void heapInsert(vector<int>&a,int index){//从index位置开始往上比较
while(index<a.size()&& a[index]>a[(index-1)/2]){
swap(a[index],a[(index-1)/2]);//(index-1)/2是index的父节点
index=(index-1)/2;
}
}
void heapSort2(vector<int>&a){
int heapSize=a.size();
// for(int i=heapSize/2;i>=0;--i){
// heapify(a,i,heapSize-1);
// }
for(int i=0;i<heapSize;++i){
heapInsert(a,i);//从0到heapSize-1依次插入到堆中
}
for(int i=heapSize-1;i>0;--i){
swap(a[0],a[i]);
heapify(a,0,i-1);
}
}
上述建堆的时间复杂度O(NlongN)
- 从下往上
一个数组就是一个完全二叉树,从最后一个非叶子结点开始往前调整结点的位置
流程:从当前位置开始往下调整,如果当前位置比最大孩子结点小,就交换,然后下移。
void heapify(vector<int>&a,int low,int high){
int i=low;
int j=2*i+1;
int temp=a[i];
while(j<=high){
while(j<high&& a[j+1]>a[j]){
++j;
}
if(a[j]>temp){
a[i]=a[j];
i=j;
j=2*i+1;
}else{
break;
}
}
a[i]=temp;
}
void heapSort2(vector<int>&a){
int heapSize=a.size();
for(int i=heapSize/2;i>=0;--i){//heapSize/2是最后一个非叶子结点
heapify(a,i,heapSize-1);
}
// for(int i=0;i<heapSize;++i){
// heapInsert(a,i);
// }
上述时间复杂度O(N)
如果元素不是同时给出的,只能采用第一种建堆的方法。
题目
给定很多线段,每个线段都有两个数[start, end],表示线段开始位置和结束位置,左右都是闭区间。
规定:
1)线段的开始和结束位置一定都是整数值
2)线段重合区域的长度必须>=1
返回线段最多重合区域中,包含了几条线段
例:
有三个线段,[1,2],[2,3],[1,3],其中[1,2]和[1,3]重合或者[2,3]和[1,3]重合,所以返回2,但是[1,2]和[2,3]不重合(因为重合的长度是0)
分析:
解1 暴力方法
先找出所有线段中开始位置的最小值minStart和结束位置的最大值maxEnd,即在[minStart,maxEnd]找包含多少条重合的线段,然后考虑有多少个线段包含minStart+0.5记为r1,有多少个线段包含minStart+1.5记为r2,……,有多少个线段包含maxEnd-0,5,取这些数的最大值即为结果。因此每个重合区域必包含某个点5。
int way1(vector<Line*>lines){
int minStart=lines[0]->start;
int maxEnd=lines[0]->end;
int re=0;
//寻找线段的最小开始和最大结束
for(int i=1;i<lines.size();++i){
if(lines[i]->start<minStart){
minStart=lines[i]->start;
}
if(lines[i]->end>maxEnd){
maxEnd=lines[i]->end;
}
}
for(double i=minStart+0.5;i<=maxEnd;++i){
int sum=0;
//遍历所有的线段,计算包含i的线段数
for(int j=0;j<lines.size();++j){
if(lines[j]->start<i && lines[j]->end>i){
++sum;
}
}
if(re<sum){
re=sum;
}
}
return re;
}
时间复杂度O((max-min)N),max是线段的最大结束,min是线段的最小开始。
解2 堆
任何重合区域的左边界必是某一个线段的左边界
- 先按线段的开始位置从小到大排序,准备一个小顶堆(存放线段的结束位置)
- 从最小的线段开始,将线段结束位置小于等于本线段开始位置的线段从堆中弹出,然后将本线段的结束位置压入堆,此时堆中的元素个数就是从本线段开始的区域中重合的线段数。
- 每一个线段都进行2过程,并同时更新重合线段数的最大值,最后返回最大值。
为什么堆中的元素个数就是从本线段开始的区域中重合的线段数?
重合的线段的结束位置一定是大于本线段的开始位置的,所以将线段结束位置小于等于本线段开始位置的线段从堆中弹出,此时堆中剩下的线段都是结束位置大于本线段开始位置,也即都是重合的线段。
bool compare(Line* a,Line* b){
return a->start<b->start;
}
int way2(vector<Line*>&lines){
priority_queue<int,vector<int>,greater<int> >q;//小根堆
int re=0;
sort(lines.begin(),lines.end(),compare);
for(int i=0;i<lines.size();++i){
int j=lines[i]->start;
while(!q.empty() && q.top()<=j){
q.pop();
}
q.push(lines[i]->end);
re=re<q.size()?q.size():re;
}
return re;
}
线段重合
分析:
注意本题重合区域没有限制,例如[1,2],[2,3]也是重合的
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
bool cmp(pair<int, int>p1, pair<int, int>p2) {
return p1.first < p2.first;//按开始位置从小到大排序
}
int way2(vector<pair<int, int>>lines) {
sort(lines.begin(), lines.end(), cmp);
priority_queue<int, vector<int>, greater<int>>pq;//存放线段结束位置的小根堆
int result = 0;
for (pair<int, int>curLine : lines) {
pq.push(curLine.second);
while (!pq.empty() && pq.top() < curLine.first) {//如果堆顶元素小于等于线段的开始位置就弹出
pq.pop();
}
result = result >= pq.size() ? result : pq.size();//更新结果
}
return result;
}
int main()
{
vector<pair<int, int>>lines;
int N;
cin>>N;
for(int i=0;i<N;i++){
pair<int,int>p;
cin>>p.first;
cin>>p.second;
lines.push_back(p);
}
cout<<way2(lines);
return 0;
}
函数指针
写法1
int sum(int a,int b)
{
return a+b;
}
int (*f)(int a,int b);
f=sum;
cout<<f(3,2);
写法2
typedef int (*fun)(int c,int d);
fun f ;//定义一个函数指针
f=sum;
cout<<f(1,2);
写法3
typedef int (fun)(int c,int d);
fun* f ;//定义一个函数指针
f=sum;
cout<<f(44,2);
手写堆
其中comp的一个实例如下:
bool myCompare(Line* a,Line*b){
return a->start<b->start;
}
#pragma once
#include<iostream>
#include<vector>
#include<unordered_map>
#include<list>
using namespace std;
//T不可以是基础类型,如果是基础类型的话需要再包一层(因为map的key不可以重复)
template<class T>
class Heap {
private:
vector<T>a;
unordered_map<T, int> map;
int heapSize;
typedef bool(*compare)(T a, T b);
compare comp;//比较器
public:
Heap(compare comp) :comp(comp) {
heapSize = 0;
}
void mySwap(int i, int j) {//交换i和j位置的数,同时索引也交换
map[a[i]] = j;
map[a[j]] = i;
swap(a[i], a[j]);
}
void heapify(int low, int high) {
int i = low;
int j = 2 * i + 1;
while (j <= high) {
while (j < high && comp(a[j + 1], a[j])) {
++j;
}
if (comp(a[j], a[i])) {
mySwap(i, j);
i = j;
j = 2 * i + 1;
}
else {
break;
}
}
}
void heapInsert(int index) {
while (comp(a[index], a[(index - 1) / 2])){
mySwap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
int getSize() {
return heapSize;
}
bool empty() {
return heapSize == 0;
}
T top() {
return a[0];
}
void pop() {
mySwap(0, --heapSize);
map.erase(a[heapSize]);
a.erase(a.begin() + heapSize);
a.resize(heapSize);
heapify(0, heapSize - 1);
}
void push(T t) {
a.resize(++heapSize);
a[heapSize - 1] = t;
map[t] = heapSize - 1;
heapInsert(heapSize-1);
}
void remove(T t) {
int index = map[t];
if (index == heapSize - 1) {
--heapSize;
a.resize(heapSize);
map.erase(t);
return;
}
mySwap(index, heapSize-1);
--heapSize;
a.resize(heapSize);
map.erase(t);
if (heapSize > 0) {
heapInsert(index);
heapify(0, heapSize - 1);
}
}
void resign(T t) {//t元素发生改动,可能需要调整堆
if (heapSize == 0) {
return;
}
int index = map[t];
heapInsert(index);
heapify(index, heapSize - 1);
}
list<T>returnList() {//返回堆中所有元素
list<T>re;
for (int i = 0; i < heapSize; ++i) {
re.push_back(a[i]);
}
return re;
}
bool contain(T t) {
return map.count(t);
}
};
相关题目
题目描述:
给定一个整形数组 int arr[] 和一个布尔类型的数组 bool op[],两个数组等长,假设长度为N,arr[i]表示客户编号,op[i]表示客户操作,op[i]=true表示客户i购买了一件商品,op[i]=false表示客户i退了一件商品。
现在要求给购买次数最多的前k名用户颁奖。所以每个事件发生前,都返回一个前k名用户名单。
得奖的规则:
- 如果某个用户购买商品次数为0,但是又发生了退货事件,则认为该事件无效,得奖名单和之前事件一致.
- 某用户发生购买商品事件,购买商品数+1,发生退货事件,购买商品数-1.
- 每次都是最多K个用户得奖,K也为传入的参数.如果根据全部规则,得奖人数确实不够K个,那就以不够的情况输出结果.
- 得奖系统分为得奖区和候选区,任何用户只要购买数>0,一定在这两个区域中的一个.
- 购买数最大的前K名用户进入得奖区,在最初时如果得奖区没有到达K个用户,那么新来的用户直接进入得奖区.
- 如果购买数不足以进入得奖区的用户,进入候选区.
- 如果候选区购买数最多的用户,已经足以进入得奖区,该用户就会替换得奖区中购买数最少的用户(大于才能替换),如果得奖区中购买数最少的用户有多个,就替换最早进入得奖区的用户.如果候选区中购买数最多的用户有多个,机会会给最早进入候选区的用户.
- 候选区和得奖区是两套时间,因用户只会在其中一个区域,所以只会有一个区域的时间,另一个没有从得奖区出来进入候选区的用户,得奖区时间删除,进入候选区的时间就是当前事件的时间(可以理解为arr[]和op中的i)从候选区出来进入得奖区的用户,候选区时间删除,进入得奖区的时间就是当前事件的时间(可以理解为arr和op们中的i).
- 如果某用户购买数==0,不管在哪个区域都离开,区域时间删除,离开是指彻底离开,哪个区域也不会找到该用户
如果下次该用户又发生购买行为,产生>0的购买数,会再次根据之前规则回到某个区域中,进入区域的时间重记.
解法1 暴力模拟
class Customer {
public:
int id;
int item;
int time;
Customer(int id) :id(id) {
item = 0;
time = 0;
}
Customer(Customer* c) {
id = c->id;
item = c->item;
time = c->item;
}
};
bool count(list<Customer*>& v, Customer* c) {//查找list是否有c
for (auto it = v.begin(); it != v.end(); ++it) {
if ((*it) == c) {
return true;
}
}
return false;
}
bool winnerCom(Customer* a, Customer* b) {
if (a->item < b->item) {
return true;
}
else if (a->item == b->item && a->time < b->time) {
return true;
}
else {
return false;
}
}
bool waitCom(Customer* a, Customer* b) {
if (a->item > b->item) {
return true;
}
else if (a->item == b->item && a->time < b->time) {
return true;
}
else {
return false;
}
}
void clearZero(list<Customer*>& s) {
auto it = s.begin();
while (it != s.end()) {
auto im = it;
++it;
if ((*im)->item == 0) {
s.erase(im);
}
}
}
void move(list<Customer*>& winner, list<Customer*>& wait, int k, int i) {
if (wait.empty()) {
return;
}
if (winner.size() < k) {//得奖区不足k个
Customer* c = wait.front();
wait.pop_front();
c->time = i;
winner.push_back(c);
}
else {
if (wait.front()->item > winner.front()->item) {
Customer* wa = wait.front();
wait.pop_front();
Customer* wi = winner.front();
winner.pop_front();
wa->time = i;
wi->time = i;
wait.push_back(wi);
winner.push_back(wa);
}
}
}
void putRe(list<list<int>>& re, list<Customer*>& winner) {
list<int>ans;
for (auto it = winner.begin(); it != winner.end(); ++it) {
Customer* lc = new Customer(*it);
ans.push_back(lc->id);
}
re.push_back(ans);
}
list<list<int>> topK(vector<int>& arr, vector<bool>& op, int k) {
unordered_map<int, Customer*>customers;//客户的id
list<Customer*> winner;//得奖区
list<list<int>>re;
list<Customer*> wait;//候选区
for (int i = 0; i< arr.size(); ++i) {
int id = arr[i];
bool buyOrRefund = op[i];
if (!buyOrRefund && !customers.count(id)) {//用户退货,并且之前未购买过
putRe(re, winner);
continue;
}
/*
接下来分以下几种情况:
1. 用户之前没有购买,此时买货
2. 用户之前购买数>0,此时买货
3. 用户之前购买数>0,此时退货
*/
if (!customers.count(id)) {
Customer* customer = new Customer(id);
customers.emplace(id, customer);
}
Customer* c = customers[id];
if (buyOrRefund) {
++c->item;
}
else {
--c->item;
}
if (c->item == 0) {
customers.erase(customers.find(id));
}
//用户购买数大于0,并且此时买货
if (!count(winner, c) && !count(wait, c)) {
if (winner.size() < k) {
c->time = i;
winner.insert(winner.end(), c);
}
else {
c->time = i;
wait.insert(wait.end(), c);
}
}
//删除购买数为0的用户
clearZero(winner);
clearZero(wait);
winner.sort(winnerCom);
wait.sort(waitCom);
move(winner, wait, k, i);
putRe(re, winner);
}
return re;
}
解法2 加强堆
class ReturnWinner {
private:
unordered_map<int, Customer*>customers;
Heap<Customer*>* winner;
Heap<Customer*>* wait;
int k;
int* a;
public:
ReturnWinner(int k) :k(k) {
winner = new Heap<Customer*>(winnerCom);
wait = new Heap<Customer*>(waitCom);
a = new int[10];
}
void operate(int time, int id, bool buyOrRefund) {
if (!buyOrRefund && !customers.count(id)) {
return;
}
//第一次购买
if (!customers.count(id)) {
Customer* c = new Customer(id);
customers.emplace(id, c);
}
Customer* c = customers[id];
if (buyOrRefund) {
c->item++;
}
else {
c->item-- ;
}
if (c->item == 0) {
customers.erase(id);
}
//第一次购买
if (!winner->contain(c) && !wait->contain(c)) {
if (winner->getSize() < k) {
c->time = time;
winner->push(c);
}
else {
c->time = time;
wait->push(c);
}
}
//之前在候选区
else if (wait->contain(c)) {
if (c->item == 0) {
wait->remove(c);
}
else {
wait->resign(c);//重新调整
}
}
//之前在获奖区
else {
if (c->item == 0) {
winner->remove(c);
}
else {
winner->resign(c);
}
}
move(time);
}
void move(int time) {
if (wait->getSize() == 0) {
return;
}
if (winner->getSize() < k) {
Customer* c = wait->top();
wait->pop();
c->time = time;
winner->push(c);
}
else {
if (wait->top()->item > winner->top()->item) {
Customer* wi = winner->top();
winner->pop();
Customer* wa = wait->top();
wait->pop();
wi->time = time;
wa->time = time;
winner->push(wa);
wait->push(wi);
}
}
}
list<int> getList() {//返回获奖用户的id
list<int>re;
list<Customer*>origin = winner->returnList();
for (auto it = origin.begin(); it != origin.end(); ++it) {
re.push_back((*it)->id);
}
return re;
}
};
list<list<int>> topK2(vector<int>& arr, vector<bool>& op, int k) {
list<list<int>>re;
ReturnWinner* returnWinner = new ReturnWinner(k);
for (int i = 0; i < arr.size(); ++i) {
returnWinner->operate(i, arr[i], op[i]);//一次交易
re.push_back(returnWinner->getList());
}
return re;
}
完整可测试的代码
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<vector>
#include<unordered_map>
#include<list>
#include<algorithm>
#include"Heap.h"
using namespace std;
class Customer {
public:
int id;
int item;
int time;
Customer(int id) :id(id) {
item = 0;
time = 0;
}
Customer(Customer* c) {
id = c->id;
item = c->item;
time = c->item;
}
};
bool count(list<Customer*>& v, Customer* c) {//查找list是否有c
for (auto it = v.begin(); it != v.end(); ++it) {
if ((*it) == c) {
return true;
}
}
return false;
}
bool winnerCom(Customer* a, Customer* b) {
if (a->item < b->item) {
return true;
}
else if (a->item == b->item && a->time < b->time) {
return true;
}
else {
return false;
}
}
bool waitCom(Customer* a, Customer* b) {
if (a->item > b->item) {
return true;
}
else if (a->item == b->item && a->time < b->time) {
return true;
}
else {
return false;
}
}
void clearZero(list<Customer*>& s) {//清楚s中item为0的元素
auto it = s.begin();
while (it != s.end()) {
auto im = it;
++it;
if ((*im)->item == 0) {
s.erase(im);
}
}
}
void move(list<Customer*>& winner, list<Customer*>& wait, int k, int i) {//调整获奖区和候选区
if (wait.empty()) {
return;
}
if (winner.size() < k) {//得奖区不足k个
Customer* c = wait.front();
wait.pop_front();
c->time = i;
winner.push_back(c);
}
else {
if (wait.front()->item > winner.front()->item) {
Customer* wa = wait.front();
wait.pop_front();
Customer* wi = winner.front();
winner.pop_front();
wa->time = i;
wi->time = i;
wait.push_back(wi);
winner.push_back(wa);
}
}
}
void putRe(list<list<int>>& re, list<Customer*>& winner) {//将winner中用户的id加入re
list<int>ans;
for (auto it = winner.begin(); it != winner.end(); ++it) {
Customer* lc = new Customer(*it);
ans.push_back(lc->id);
}
re.push_back(ans);
}
//暴力模拟
list<list<int>> topK(vector<int>& arr, vector<bool>& op, int k) {
unordered_map<int, Customer*>customers;//客户的id
list<Customer*> winner;//得奖区
list<list<int>>re;
list<Customer*> wait;//候选区
for (int i = 0; i< arr.size(); ++i) {
int id = arr[i];
bool buyOrRefund = op[i];
if (!buyOrRefund && !customers.count(id)) {//用户退货,并且之前未购买过
putRe(re, winner);
continue;
}
/*
接下来分以下几种情况:
1. 用户之前没有购买,此时买货
2. 用户之前购买数>0,此时买货
3. 用户之前购买数>0,此时退货
*/
if (!customers.count(id)) {
Customer* customer = new Customer(id);
customers.emplace(id, customer);
}
Customer* c = customers[id];
if (buyOrRefund) {
++c->item;
}
else {
--c->item;
}
if (c->item == 0) {
customers.erase(customers.find(id));
}
//用户购买数大于0,并且此时买货
if (!count(winner, c) && !count(wait, c)) {
if (winner.size() < k) {
c->time = i;
winner.insert(winner.end(), c);
}
else {
c->time = i;
wait.insert(wait.end(), c);
}
}
//删除购买数为0的用户
clearZero(winner);
clearZero(wait);
winner.sort(winnerCom);
wait.sort(waitCom);
move(winner, wait, k, i);
putRe(re, winner);
}
return re;
}
//自动生成数组
void geneArr(vector<int>& arr, vector<bool>& op, int num) {
srand((unsigned int)time(0));
arr.resize(num);
op.resize(num);
for (int j = 0; j < num; ++j) {
int val = rand() % 5 + 1;
arr[j] = val;
if ((j & 1) == 0) {
op[j] = true;
}
else {
op[j] = false;
}
}
}
//方法2 加强堆
class ReturnWinner {
private:
unordered_map<int, Customer*>customers;
Heap<Customer*>* winner;
Heap<Customer*>* wait;
int k;
int* a;
public:
ReturnWinner(int k) :k(k) {
winner = new Heap<Customer*>(winnerCom);
wait = new Heap<Customer*>(waitCom);
a = new int[10];
}
void operate(int time, int id, bool buyOrRefund) {
if (!buyOrRefund && !customers.count(id)) {
return;
}
//第一次购买
if (!customers.count(id)) {
Customer* c = new Customer(id);
customers.emplace(id, c);
}
Customer* c = customers[id];
if (buyOrRefund) {
c->item++;
}
else {
c->item-- ;
}
if (c->item == 0) {
customers.erase(id);
}
//第一次购买
if (!winner->contain(c) && !wait->contain(c)) {
if (winner->getSize() < k) {
c->time = time;
winner->push(c);
}
else {
c->time = time;
wait->push(c);
}
}
//之前在候选区
else if (wait->contain(c)) {
if (c->item == 0) {
wait->remove(c);
}
else {
wait->resign(c);//重新调整
}
}
//之前在获奖区
else {
if (c->item == 0) {
winner->remove(c);
}
else {
winner->resign(c);
}
}
move(time);
}
void move(int time) {
if (wait->getSize() == 0) {
return;
}
if (winner->getSize() < k) {
Customer* c = wait->top();
wait->pop();
c->time = time;
winner->push(c);
}
else {
if (wait->top()->item > winner->top()->item) {
Customer* wi = winner->top();
winner->pop();
Customer* wa = wait->top();
wait->pop();
wi->time = time;
wa->time = time;
winner->push(wa);
wait->push(wi);
}
}
}
list<int> getList() {//返回获奖用户的id
list<int>re;
list<Customer*>origin = winner->returnList();
for (auto it = origin.begin(); it != origin.end(); ++it) {
re.push_back((*it)->id);
}
return re;
}
};
list<list<int>> topK2(vector<int>& arr, vector<bool>& op, int k) {
list<list<int>>re;
ReturnWinner* returnWinner = new ReturnWinner(k);
for (int i = 0; i < arr.size(); ++i) {
returnWinner->operate(i, arr[i], op[i]);//一次交易
re.push_back(returnWinner->getList());
}
return re;
}
bool cmp(list<int>& re1,list<int>& re2) {
re1.sort();
re2.sort();
auto it1 = re1.begin();
auto it2 = re2.begin();
while (it1 != re1.end() && it2!=re2.end()) {
if ((*it1) != (*it2)) {
return false;
}
++it1;
++it2;
}
return true;
}
int main() {
int testTime = 100;
//比较器
for (int i = 0; i < testTime; ++i) {
vector<int> arr;
vector<bool> op;
geneArr(arr, op, 60);
list<list<int>>re1 = topK(arr, op, 3);
list<list<int>>re2 = topK2(arr, op, 3);
auto it1 = re1.begin();
auto it2 = re2.begin();
while (it1 != re1.end() && it2 != re2.end()) {
if (!cmp(*it1, *it2)) {
cout << "error" << endl;
}
++it1;
++it2;
}
}
return 0;
}