CD53.【C++ Dev】模拟实现优先级队列(含仿函数)
目录
1.知识回顾
2.看看STL的堆
3.仿函数
特点1: 类似函数
特点2: 能保存状态
4.容器适配器的复习
5.模拟实现优先级队列
框架代码
构造函数
向下调整函数
测试代码:使用std的仿函数
测试代码:传入自制的仿函数
测试代码:使用自定义类型建堆
size
empty
pop
push
向上调整函数
top
6.LeetCode上的题
1.知识回顾
堆的复习:
101.【C语言】数据结构之二叉树的堆实现(顺序结构) 1
102.【C语言】数据结构之二叉树的堆实现(顺序结构) 2
103.【C语言】数据结构之用堆对数组排序
104.【C语言】数据结构之建堆的时间复杂度分析
118.【C语言】数据结构之排序(堆排序和冒泡排序)
之前在CC38.【C++ Cont】模拟实现堆文章写过堆的模拟实现,只不过没有带上模版,而且比较粗糙,这里重新写
2.看看STL的堆
看看框架:
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class T, class Sequence = vector<T>, class Compare = less<typename Sequence::value_type> >
#else
template <class T, class Sequence, class Compare>
#endif
class priority_queue
{//......
}
Sequence是容器适配器,Compare可以传入仿函数
3.仿函数
stackoverflow网仿函数functors(不是functions)给出的定义:
A functor is pretty much just a class which defines the
operator()
. That lets you create objects which "look like" a function.
特点1: 类似函数
仿函数是有operator()成员函数的类,能起到类似函数("仿")的作用.不是函数却可以像函数一样调用
例如以下的例子:
class Less
{
public:bool operator()(const int& x, const int& y) const{return x < y;}
};
测试代码:
下面是使用仿函数完整的写法,Less第一个字母大写,为了和std命名空间的less区分
int main()
{Less lessfunction;//先实例化//再调用operator()cout << lessfunction.operator()(2, 3);return 0;
}
运行结果:
当然可以简写:类的对象可以像函数一样使用,替代函数指针
lessfunction(2, 3);//写法类似函数
也可以写成匿名对象(即Less())的形式:
Less()(2, 3);
当然也可以加入模版参数:
#include <iostream>
using namespace std;
template<class T>
class Less
{
public:bool operator()(const T& x, const T& y) //const{return x < y;}
};
int main()
{Less<int> lessfunction;//类要显式实例化cout << lessfunction(2, 3);return 0;
}
运行结果:
特点2: 能保存状态
以stackoverflow what-are-c-functors-and-their-uses网的回答的代码为例:
#include <algorithm>
#include <vector>
struct add_x
{add_x(int val) : x(val) {} int operator()(int y) const { return x + y; }private:int x;
};int main()
{std::vector<int> in = { 1,2,3 };std::vector<int> out(in.size());std::transform(in.begin(), in.end(), out.begin(), add_x(1));return 0;
}
运行结果:
(注:transform函数的作用是将容器中的元素进行某种形式的转换并将结果存储到另一个容器中)
这里使用其中一种调用方法:
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
1.transform的第三个参数是对单个元素应用的一元操作函数或函数对象
2. add_x(1)先用1去构造add_x匿名对象: add_x(int val) : x(val) {} ,这样私有变量x的值为1
3.int operator()(int y) const { return x + y; }可以看出会对每一个元素都加x
仿函数比普通函数要好的一点是仿函数内部可以保存状态,例如上方的私有变量x经过初始化就不变了,如果使用函数就必须写一个恰好把参数加 x 的函数,这样比较麻烦,而上方的仿函数是通用的,它会加上你在初始化时指定的任意值
而且某些情况下仿函数会被编译器内联:
#include <iostream>
struct add_x
{add_x(int val) : x(val) {}int operator()(int y) const { return x + y; }private:int x;
};int main()
{int y = 0;std::cin >> y;y=add_x(1).operator()(y);std::cout << y;return 0;
}
反汇编分析:没有出现call指令,说明被内联了
4.容器适配器的复习
参见CD49.【C++ Dev】容器适配器模式(含typename的用法)文章
5.模拟实现优先级队列
对标STL库,使用STL的vector和less
框架代码
#pragma once
namespace mystl
{template <class T, class Container = std::vector<T>,class Compare = std::less<T> >class priority_queue{public://......private:Container _con;};
}
剩下只需要实现成员函数:
构造函数
使用迭代区间:[first,last),先讲元素存到容器适配器中,然后对容器适配器建堆
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{while (first != last){_con.push_back(*first);first++;}adjust_down(Container _con);
}
向下调整函数
之前在102.【C语言】数据结构之二叉树的堆实现(顺序结构) 2文章中写过:
void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (a[child + 1] > a[child]){child++;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = child * 2 + 1;}else{break;}}
}
但需要改:
1.需要借助仿函数Compare,不能写<或>
2.child+1可能会越界,需要检查
使用短路运算的特性即可 if (child+1<_con.size() && _cmp(_con[child], _con[child + 1]))
3.默认情况下是建立大堆,调整下比较顺序
_cmp(_con[child], _con[child + 1])
_cmp(_con[parent], _con[child])
namespace mystl
{template <class T, class Container = std::vector<T>,class Compare = std::less<T> >class priority_queue{public:template<class InputIterator>priority_queue(InputIterator first, InputIterator last){while (first != last){_con.push_back(*first);first++;}for (int i = (_con.size() - 2) / 2; i >= 0; i--){adjust_down(i);}}private:Container _con;Compare _cmp;void adjust_down(int parent){int child = parent * 2 + 1;while (child < _con.size()){if (child+1<_con.size() && _cmp(_con[child], _con[child + 1])){child++;}if (_cmp(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);parent = child;child = child * 2 + 1;}else{break;}}}};
}
测试代码:使用std的仿函数
#include "queue.h"
int main()
{std::vector<int> arr = { 1,3,2,5,9 };mystl::priority_queue<int> pq(arr.begin(),arr.end());return 0;
}
运行结果:
测试代码:传入自制的仿函数
#include "queue.h"
template<class T>
struct mygreater
{
public:bool operator()(T& x, T& y){return x > y;}
};
int main()
{std::vector<int> arr = { 1,3,2,5,9 };mystl::priority_queue<int,std::vector<int>, mygreater<int>> pq(arr.begin(),arr.end());return 0;
}
运行结果:
测试代码:使用自定义类型建堆
例如之前CD20.【C++ Dev】类和对象(11) 日期类对象的成员函数(++、--、日期-日期)文章写的日期类,需要手动写仿函数
#include "queue.h"
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}Date(const Date& x){_year = x._year;_month = x._month;_day = x._day;}bool operator< (const Date& d2) const{if (_year < d2._year)return true;else if (_year == d2._year && _month < d2._month)return true;else if (_year == d2._year && _month == d2._month && _day < d2._day)return true;return false;}
private:int _year;int _month;int _day;
};int main()
{std::vector<Date> arr = { Date(2025, 7, 15),Date(2024, 6, 15),Date(2025, 9, 15),Date(2026, 1, 1) };mystl::priority_queue<Date,std::vector<Date>, std::less<Date>> pq(arr.begin(),arr.end());return 0;
}
有operator<,可以用less
运行结果:
size
返回堆的大小
size_t size()
{return _con.size();
}
empty
判断堆是否为空
bool empty()
{return _con.empty();
}
pop
弹出堆顶元素,注意先让首元素和尾元素交换,这样才能尾删
void pop()
{std::swap(_con[0], _con[size() - 1]);_con.pop_back();adjust_down(0);
}
测试代码:
#include "queue.h"
int main()
{std::vector<int> arr = { 1,3,2,5,9 };mystl::priority_queue<int,std::vector<int>, std::less<int>> pq(arr.begin(),arr.end());pq.pop();return 0;
}
运行结果:
push
尾插后向上调整
void push(const T& x)
{_con.push_back(x);adjustup(_con.size() - 1);
}
向上调整函数
之前在101.【C语言】数据结构之二叉树的堆实现(顺序结构) 1文章中写过:
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0){if (a[child] > a[parent]){Swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
稍微改改:
1.需要借助仿函数Compare,不能写<或>
3.默认情况下是建立大堆,调整下比较顺序
void adjust_up(int child)
{int parent = (child - 1) / 2;while (child > 0){if (_cmp(_con[parent],_con[child])){std::swap(_con[parent], _con[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
测试代码:
#include "queue.h"
int main()
{std::vector<int> arr = { 1,3,2,5,9 };mystl::priority_queue<int,std::vector<int>, std::less<int>> pq(arr.begin(),arr.end());pq.push(10);return 0;
}
运行结果:
top
返回堆顶元素
T& top()
{return _con[0];
}
6.LeetCode上的题
使用自制的priority_queue
namespace mystl
{template <class T, class Container = std::vector<T>,class Compare = std::less<T> >class priority_queue{public:template<class InputIterator>priority_queue(InputIterator first, InputIterator last){while (first != last){_con.push_back(*first);first++;}for (int i = (_con.size() - 2) / 2; i >= 0; i--){adjust_down(i);}}size_t size(){return _con.size();}bool empty(){return _con.empty();}void pop(){std::swap(_con[0], _con[size() - 1]);_con.pop_back();adjust_down(0);}void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}T& top(){return _con[0];}private:Container _con;Compare _cmp;void adjust_down(int parent){int child = parent * 2 + 1;while (child < _con.size()){if (child+1<_con.size() && _cmp(_con[child], _con[child + 1])){child++;}if (_cmp(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);parent = child;child = child * 2 + 1;}else{break;}}}void adjust_up(int child){int parent = (child - 1) / 2;while (child > 0){if (_cmp(_con[parent],_con[child])){std::swap(_con[parent], _con[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}}};
}class Solution {
public:int findKthLargest(vector<int>& nums, int k) {mystl::priority_queue<int,vector<int>,greater<int>> pq(nums.begin(),nums.begin()+k);for (int j=k;j<nums.size();j++){if (pq.top()<nums[j]){pq.pop();pq.push(nums[j]);}}return pq.top();}
};
提交结果: