目录
1.优先队列的类模板
2.仿函数的讲解
3.成员变量
4.构造函数
5。判空,返回size,返回队头
6.插入
7.删除
1.优先队列的类模板
我们先通过模板来进行初步了解
由上图可知,我们的模板里有三个参数,第一个参数自然就是你要存储的数据的类型了;第二个参数是我们的适配器,适配器可以手动更改,但我们这里就用它默认的vector,这就意味着我们的优先队列是由我们的顺序表容器来实现的;第三个参数是我们的仿函数,仿函数是我们这个优先队列的重要知识点,等会我会详细说明。
我先把代码全放出来,这样方便不理解的时候可以看到全部代码来思考。
我提前说明一下,优先队列的本质其实就是vector和堆的性质。
template<class T> class less { public: bool operator()(const T& a, const T& b) { return a < b; } }; template<class T> class greater { public: bool operator()(const T& a, const T& b) { return a > b; } }; template<class T,class Container=vector<T>,class comper=less<T>> class priority_queue { public: //构造 priority_queue() {} //拷贝构造 //模板函数 //迭代器版 template <class InputIterator> priority_queue(InputIterator first, InputIterator last) { while (first != last) { push(*first); first++; } } //判空 bool empty() const { return _con.empty(); } //返回size size_t size() const { return _con.size(); } //返回队头 const T& top() { return _con[0]; } //插入 //向上调整 void adjustup(size_t child) { size_t parent = (child - 1) / 2; while (child > 0) { if (cmp(_con[parent], _con[child])) { swap(_con[parent], _con[child]); child = parent; parent = (child - 1) / 2; } else break; } } void push(const T& x) { _con.push_back(x); adjustup(_con.size()-1); } //删除 //向下调整 void adjustdown(size_t parent) { size_t 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])) { swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else break; } } void pop() { swap(_con[0], _con[_con.size() - 1]); _con.pop_back(); adjustdown(0); } private: Container _con; comper cmp; };
2.仿函数的讲解
仿函数是什么意思呢,我们不妨猜测一下,通过它的名字,仿函数似乎是一个模仿函数的存在,这个猜测其实就是对的。仿函数没有什么门道,它的底层就是进行了一个运算符重载,重载的是(),所以调用的时候感觉像是在调用函数一样。
template<class T> class less { public: bool operator()(const T& a, const T& b) { return a < b; } };
我们可以通过上面的代码发现我们的类叫less,里面重载了一个()运算符,使其能达到判断较小值的目的;
template<class T> class greater { public: bool operator()(const T& a, const T& b) { return a > b; } };
有了判断较小值,自然就有判断较大值,原理也是跟上面一样。
有了仿函数我们就能更加方便且安全的对自定义类型的数据进行有意义的大小比较。
3.成员变量
private: Container _con; comper cmp;
成员变量我们自然还是设置成私有的,这两个成员变量的类型是不是有一点懵逼?其实是我们的类模板声明的。
template<class T,class Container=vector<T>,class comper=less<T>>
所以我们的第一个成员变量的类型是我们的vector,第二个是我们的仿函数类型的数据。
4.构造函数
//构造 priority_queue() {}
其实我们仔细想一想就能明白,我们的优先队列的底层既然是vector和堆,而我们的优先队列存在的意义就像是给vector再加工一下,实现堆的性质和功能,所以我们的优先队列甚至不需要自己实现构造函数,直接让它自动调用vector的就行了。析构函数也是一样的道理,我们既然不需要写构造函数,那么析构函数直接不定义了,调用系统默认的就可以了。
5。判空,返回size,返回队头
//判空 bool empty() const { return _con.empty(); } //返回size size_t size() const { return _con.size(); } //返回队头 const T& top() { return _con[0]; }
这三个功能真的没有什么好解释的,大家一看就能理解,这不都是复用了vector的功能嘛。
6.插入
//插入 //向上调整 void adjustup(size_t child) { size_t parent = (child - 1) / 2; while (child > 0) { if (cmp(_con[parent], _con[child])) { swap(_con[parent], _con[child]); child = parent; parent = (child - 1) / 2; } else break; } } void push(const T& x) { _con.push_back(x); adjustup(_con.size()-1); }
插入功能就是这个优先队列不同于顺序表的地方之一了,因为是堆的性质,所以我们插入的时候肯定不能单纯的插入,我们要进行向上调整,向上调整的目的就是把我们的vector打造成堆的模样。我们就来讲解一下向上调整
我们这次建的是大堆,大堆是什么意思呢?大堆就是我们的父亲节点要大于我们的孩子节点,所以如果我们的父亲节点小于孩子节点我们就交换父亲节点和孩子节点的值。
仿函数的用法就出现了
cmp(_con[parent], _con[child])
不说的话大家是不是就以为cmp是我们的普通函数了?仿函数的妙用就在这里。
然后我再说明一下,孩子节点和父亲节点是如何来确定的,我们知道顺序表的物理结构也是连续的,下标从0开始,我们的父亲节点只需要*2加1就能找到左孩子,再加一就能找到右孩子,而我们的不管是左孩子还是右孩子都只需要-1再除2就能找到父亲了,这其中的数学原理大家下去画画图,很快就能得出这个结论了。
7.删除
//删除 //向下调整 void adjustdown(size_t parent) { size_t 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])) { swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else break; } } void pop() { swap(_con[0], _con[_con.size() - 1]); _con.pop_back(); adjustdown(0); }
删除用的就是我们的向下调整了。我们的优先队列的数据是连续的,从头部删除性能肯定不好,所以我们的思路是先把要删除的元素跟最后一个交换,然后删除最后一个元素就能达到我们的效果了,这个时候为了保证我们堆的性质要对首元素进行调整,调整到它该去的位置。