文章目录
- 框架
- (constructor)
- size()、empty()、top()
- push()
- 声明
- 参数
- 实现
- 代码
- pop()
- 声明
- 实现
- 代码
- 仿函数与函数指针
- 仿函数的定义
- 仿函数实现回调
- 函数指针实现回调
- adjust_up 和 adjust_down 的改进
- 完整代码
容器适配器(Container Adapter)是一种 C++ 中的抽象数据类型,它提供了一种在指定底层容器基础上进行封装,以实现特定功能的方式。容器适配器并不是独立的容器类型,而是建立在其他容器之上的封装,通过提供不同的接口或限制来满足特定需求。
priority_queue(优先级队列)就是一个容器适配器,是数据结构上的堆(Heap)的实现,库中的声明如下:
template <class T, class Container = vector<T>,class Compare = less<typename Container::value_type> > class priority_queue;
- T:指数据元素的类型。
- Container :指存储元素的内部基础容器对象的类型,默认以 vector 进行适配。
- Compare :仿函数对象的类型。(什么是仿函数后面会说)
基础容器可以是库里有的,也可以是自己实现的,但都应当满足以下要求:
- 要求应可通过随机访问迭代器访问
- 支持以下内容 operations:
- empty()
- size()
- front()
- push_back()
- pop_back()
框架
本次模拟实现只是,简单的模拟实现,旨在加深对 priority_queue 的理解和了解和使用仿函数,主要参考 C++98 版本的 priority_queue。进行模拟实现。
priority_queue 类的大致框架如下:
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:// priority_queue 该提供以下接口priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare());template <class InputIterator>priority_queue(InputIterator first, InputIterator last);bool empty() const;size_t size() const;const T& top() const;void push(const T& x);void pop();private:void adjust_up(size_t child);void adjust_down(size_t parent);private:Container _c;Compare _comp;
};
(constructor)
C++98版本的 priority_queue 的构造函数重载有两个版本,一个是全缺省的默认构造函数,另一个是迭代器区间构造函数。
// 全缺省默认构造
priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare()): _c(ctnr), _comp(comp)
{}// 迭代器范围区间构造
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)// 先调用 Container 对象的 range constructor: _c(first, last)
{// 从最后一个非叶子结点开始向下调整成堆结构int count = size();int root = ((count - 2) >> 1);for (; root >= 0; --root){adjust_down(root);}
}
size()、empty()、top()
这三个不是重点,且实现起来比较简单。
size_t size() const
{return _c.size();
}bool empty() const
{return _c.empty();
}// 堆顶数据不可被修改,堆顶元素被修改会破坏堆的特性
const T& top() const
{return _c.front();
}
push()
声明
void push (const T& val);
参数
形参 val 是待插入对象
val 类型是 const T&,引用传参是为了降低传参消耗。
实现
- 先将 val 插入到堆的末尾,即最后一个孩子之后。
- 插入之后如果堆的性质遭到破坏,将新插入结点顺着双亲往上调整到合适位置。
代码
void push(const T& x)
{// 1. 对象插入数据_c.push_back(x);// 2. 向上调整adjust_up(_c.size() - 1);
}// 向上调整算法
void adjust_up(size_t child)
{size_t parent = (child - 1) / 2;while (child > 0){if (_c[parent] < _c[child]){swap(_c[parent], _c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
pop()
声明
void pop();
实现
- 将堆顶元素与堆中的最后一个元素进行交换。
- 删除堆中最后一个元素。
- 从堆顶元素位置开始向下调整到满足堆特性为止。
代码
void pop()
{if (empty())return;// 交换堆顶和末尾swap(_c[0], _c[_c.size() - 1]);// 删除末尾位置元素_c.pop_back();// 堆顶位置向下调整adjust_down(0);
}// 向下调整算法
void adjust_down(size_t parent)
{size_t child = (parent * 2) + 1;while (child < _c.size()){if (child + 1 < _c.size() && _c[child] < _c[child + 1]){child += 1;}if (_c[parent] < _c[child]){swap(_c[parent], _c[child]);parent = child;child = (parent * 2) + 1;}else{break;}}
}
仿函数与函数指针
// adjust_up
if (_c[parent] < _c[child])// adjust_down
if (child + 1 < _c.size() && _c[child] < _c[child + 1])
if (_c[parent] < _c[child])
这几句代码写死了,priority_queue 实现的堆只能是大堆,但是通过直接修改源代码的方式来进行大小堆的切换是不现实的,更推荐的做法是通过回调函数控制比较逻辑,C语言采用函数指针实现,C++更喜欢使用仿函数(函数对象)。
仿函数的定义
仿函数(Functor)是一种行为类似函数的对象,类中重载了函数调用运算符 operator(),通过这种方式,对象就可以像函数一样被调用。
class less
{
public:bool operator()(int x, int y){return x < y;}
};
int main()
{// 实例化对象 lessfuncless lessfunc;// 调用成员函数cout << lessfunc(1, 2) << endl;cout << lessfunc.operator()(1, 2) << endl;return 0;
}
乍一看我们会认为lessfunc是函数名,但其实它是一个对象,它通过运算符重载让这个对象能够仿造函数的形式来使用。
仿函数实现回调
现在有一个 A 类,类中有一个成员方法 func() 作用是比较两个整型数据的大小,func() 通过使用仿函数回调 less 类对象、greater 类对象中的方法来实现 func() 中比较逻辑的控制,做法如下:
class less
{
public:bool operator()(int x, int y){return x < y;}
};class greater
{
public:bool operator()(int x, int y){return x > y;}
};template<class Compare>
class A
{
public:// 功能:比较两个int数据的大小,返回比较结果void func(int xx, int yy){Compare com;cout << com(xx, yy) << endl;}
};int main()
{A<less> a1;a1.func(100, 200);A<less> a2;a2.func(100, 200);return 0;
}
函数指针实现回调
现在有一个 A 类,类中有一个成员方法 func() 作用是比较两个整型数据的大小,func() 通过函数指针回调全局函数 lessfc、greaterfc 来控制 func() 的比较逻辑,做法如下:
bool lessfc(int x, int y)
{return x < y;
}bool greaterfc(int x, int y)
{return x > y;
}// A 类回调 lessfc、greater
class A
{
public:A(bool(*pf)(int, int)):_pf(pf){}// 控制 xx,yy 的比较逻辑void func(int xx, int yy){cout << _pf(xx, yy) << endl;}private:bool(*_pf)(int, int);
};int main()
{A a(lessfc);// 比较大小cout << a.func(100, 200) << endl;A a(greaterfc);// 比较大小cout << a.func(100, 200) << endl;return 0;
}
相较于仿函数实现,函数指针实现有几个缺陷:
- 函数指针类型写起来复杂。
- 函数指针只能通过函数参数的形式来传递,这就被迫要求实现构造函数,同时还要用一个成员变量来保存传递进来的函数指针,以便使用。
adjust_up 和 adjust_down 的改进
template<class T>
class less
{
public:bool operator()(const T& x, const T& y){return x < y;}
};template<class T>
class greater
{
public:bool operator()(const T& x, const T& y){return x > y;}
};
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:// push()...// pop()...
private:void adjust_up(size_t child){size_t parent = (child - 1) / 2;while (child > 0){//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent)
{size_t child = (parent * 2) + 1;while (child < _c.size()){//if (child + 1 < _c.size() && _c[child] < _c[child + 1])if (child + 1 < _c.size() && _comp(_c[child], _c[child + 1])){child += 1;}//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);parent = child;child = (parent * 2) + 1;}else{break;}}
}private:Container _c;Compare _comp;
};
完整代码
namespace ljh
{#include<vector>template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};template <class T, class Container = vector<T>, class Compare = less<T> >class priority_queue{public:priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare()): _c(ctnr), _comp(comp){}template <class InputIterator>priority_queue(InputIterator first, InputIterator last)// 先调用 Container 对象的 range constructor: _c(first, last){// 从最后一个非叶子结点开始向下调整成堆结构int count = size();int root = ((count - 2) >> 1);for (; root >= 0; --root){adjust_down(root);}}bool empty() const{return _c.empty();}size_t size() const{return _c.size();}// 堆顶数据不可被修改,堆顶元素被修改会破坏堆的特性const T& top() const{return _c.front();}void push(const T& x){// 1. 对象插入数据_c.push_back(x);// 2. 向上调整adjust_up(_c.size() - 1);}void pop(){if (empty())return;// 交换堆顶和末尾swap(_c[0], _c[_c.size() - 1]);// 删除末尾位置元素_c.pop_back();// 堆顶位置向下调整adjust_down(0);}private:void adjust_up(size_t child){size_t parent = (child - 1) / 2;while (child > 0){//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent){size_t child = (parent * 2) + 1;while (child < _c.size()){//if (child + 1 < _c.size() && _c[child] < _c[child + 1])if (child + 1 < _c.size() && _comp(_c[child], _c[child + 1])){child += 1;}//if (_c[parent] < _c[child])if (_comp(_c[parent], _c[child])){swap(_c[parent], _c[child]);parent = child;child = (parent * 2) + 1;}else{break;}}}private:Container _c;Compare _comp;};
}