priority_queue模拟实现
目录
介绍
模拟实现
无参构造函数
迭代器构造
push插入
pop删除
top返回队首元素
返回队列元素个数
判空
仿函数
补充:反向迭代器
模拟实现反向迭代器
构造和析构
重载++和--
解引用*和地址访问->
!=
反向迭代器在容器中实现
补充练习
数组中第K大元素
介绍
在C++的<queue>头文件中一共定义了两个容器,准确来说是容器适配器,分别是queue队列和priority_queue优先级队列,两者比较相似,主要区别是priority_queue会对数据进行排序,会将数据中的最大值或最小值放再头部,其他基本上没有区别。
priority_queue优先级队列与数据结构堆比较相似。
关于queue队列和heap堆下面有详细介绍。
二叉树(C语言)_二叉树 csdn-CSDN博客文章浏览阅读1.4k次,点赞22次,收藏20次。帮助读者快速掌握树这一数据结构,了解堆的功能,能够实现堆排序,以及如何再大量数据中快速找到前K个最大元素,如何处理普通二叉树,普通二叉树的遍历等知识。_二叉树 csdnhttps://blog.csdn.net/2401_87944878/article/details/145262931
std::stack和std::queue-CSDN博客文章浏览阅读818次,点赞26次,收藏37次。本文通过对stack和queue两个容器适配器的模拟实现,对deque容器的解释来让读者能够更好的理解使用stack和queue的功能,文章结尾配有习题来供读者练习。https://blog.csdn.net/2401_87944878/article/details/146024780
模拟实现
前面说过priority_queue是容器适配器,所以在模板参数的位置也需要添加一个容器的模板参数。其默认迭代器是vector。
无参构造函数
template<class T,class Container=vector<T>>
class priority_queue
{
public:
priority_queue() //类的成员是自定义类型,会调用其自己的构造函数
{
}
private:
Container _con;
};
迭代器构造
迭代器构造就要将数据依次插入,然后调整。
此处采用向下调整的方式,因为向下调整的效率是要高于向上调整的,具体请看二叉树(C语言)_二叉树 csdn-CSDN博客文章浏览阅读1.4k次,点赞22次,收藏20次。帮助读者快速掌握树这一数据结构,了解堆的功能,能够实现堆排序,以及如何再大量数据中快速找到前K个最大元素,如何处理普通二叉树,普通二叉树的遍历等知识。_二叉树 csdnhttps://blog.csdn.net/2401_87944878/article/details/145262931
//向下调整
void AdjustDown(int parent)
{
int child = 2 * parent + 1;
while (child < _con.size())
{
//对于堆即满二叉树来说,一个父节点有两个子节点,所以要对左右节点进行比较
if (child + 1 < _con.size() && _con[child + 1] > _con[child])
{
++child;
}
//子节点大于父节点,子节点向上调整
if (_con[child] > _con[parent])
{
std::swap(_con[child], _con[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
//迭代器构造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
//先将数据插入
while (first != last)
{
_con.push_back(*first);
++first;
}
//将数据进行调整
for (int i = _con.size() - 1; i >= 0; --i)
{
//进行向下调整
AdjustDown(i);
}
}
push插入
此处我们以大堆来模拟实现优先级队列(最大元素在队首)。
优先级队列本质是堆,即二叉树,此处要用到父节点和子节点,代码实现是直接使用。对代码有疑问可转至二叉树(C语言)_二叉树 csdn-CSDN博客。
优先级队列的插入就是,先将数据插入到尾部,再对新数据向上调整,找到新数据的合适位置。
//向上调整
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[child] > _con[parent]) //子节点大于父节点,将子节点向上调整
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& val)
{
//将数据插入到尾部
_con.push_back(val);
//进行向上调整
AdjustUp(_con.size()-1);
}
此处如果想要建小堆,应该怎么办???再写一个优先级队列???
此处可以使用仿函数实现,结尾会讲解。
pop删除
pop删除的是队首的元素。
pop的时候为了防止堆的整体结构被破坏。先将队首元素和队尾元素交换,将尾节点pop再对队首元素进行向下调整,找到其目标位置。
//向下调整
void AdjustDown(int parent)
{
int child = 2 * parent + 1;
while (child < _con.size())
{
//对于堆即满二叉树来说,一个父节点有两个子节点,所以要对左右节点进行比较
if(child+1<_con.size()&&_con[child+1]>_con[child])
{
++child;
}
//子节点大于父节点,子节点向上调整
if (_con[child] > _con[parent])
{
std::swap(_con[child], _con[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void pop()
{
//先将队首和队尾元素进行交换
std::swap(_con[0], _con[_con.size() - 1]);
//将队尾元素pop
_con.pop_back();
//对队首元素进行向下调整
AdjustDown(0);
}
top返回队首元素
T& top()
{
return _con[0];
}
返回队列元素个数
size_t size()
{
return _con.size();
}
判空
bool empty()
{
return _con.size() == 0;
}
仿函数
上面我们实现了一个大堆的优先级队列,那如果要实现小堆的优先级队列应该怎么办???再写一个类???再写一个类是可以实现的,但是代码长度会变大也是毫无疑问的。因此引入了仿函数。
仿函数虽然叫函数但是其实际上是自定义类。只不过这个类可以像函数一样使用。
下面实现一个比较是否小于的仿函数。
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
可以看到以上代码只是实现了一个<小于运算符重载,但是在外部只需要定义一个Less类就可以直接使用该比较。
Less<int> com; //定义一个类
int a = 10;
int b = 20;
com(a, b);
可以看到以上是调用的类,com(a,b)就像调用函数一样,因此将其命名为仿函数。
因此在实现优先级队列的时候需要再添加一个类,用于比较,来决定是大堆还是小堆。
因此上面的模拟实现需要修改。
需要再添加一个私有成员,来定义一个比较类的对象。
private:
Container _con;
Comp _com;
对模板进行修改。
template<class T,class Container=vector<T>,class Comp=Less<T>>
对向上,向下调整的比较进行修改。
if (_com(_con[child] , _con[parent]))
补充:反向迭代器
反向迭代器与正向迭代器一样可以是内置类型的指针,也可以是类。库中的反向迭代器是用类实现的,直接调用正向迭代器模拟实现。
模拟实现反向迭代器
正向迭代器的区间和反向迭代器的区间是镜像对称的。
库中的反向迭代器的++是调用这里其对应的正向迭代器的--实现,而--就用++实现。正向的begin()是反向的rend(),正向的end()是反向的rbegin(),所以在重载*和重载&的时候要注意位置。
构造和析构
反向迭代器与正向迭代器相比,反向迭代器多了一个模板参数iteartor,来接受不同的正向迭代器。
//模拟实现反向迭代器
template<class Ref ,class Ptr ,class Iterator>
class Reverse_iterator
{
typedef Reverse_iterator<Ref, Ptr, Iterator> Self;
public:
Reverse_iterator(const Iterator& it)
:_it(it)
{}
~Reverse_iterator()
{
}
private:
Iterator _it; //反向迭代器的成员是正向迭代器所以不需要写其构造和析构会自动调用。
};
重载++和--
反向迭代器的++就是正向迭代器的--,反向迭代器的--同理。
//重载++
Self& operator++()
{
--_it;
return *this;
}
//重载--
Self& operator--()
{
++_it;
return *this;
}
解引用*和地址访问->
注意反向迭代器与正向迭代器是进行对称的,正向迭代器是左闭右开的所以反向迭代器是左开右闭的,因此在解引用的时候应给是对右边一个数进行操作的。
//重载*解引用
Ref operator*()
{
return *(--_it); //先--后解引用
}
//重载->访问
Ptr operator->()
{
return &(operator*());
}
!=
//重载!=
bool operator!=(const Self& rit)
{
return _it == rit._it;
}
反向迭代器在容器中实现
//实现反向迭代器
//非const版本
typedef Reverse_iterator<T&, T*, iterator> reverse_iterator;
typedef Reverse_iterator<const T&, const T*, const_iterator> const_reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end()); //临时变量
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
//const版本
const_reverse_iterator rbegin()const
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend()const
{
return const_reverse_iterator(begin());
}
补充练习
数组中第K大元素
数组中的第K个最大元素https://leetcode.cn/problems/kth-largest-element-in-an-array/
解法:只用优先级队列,设置一个有k个元素的小堆优先级队列,先放入k个元素,再依次将数组中比队首元素小的交换。最后堆顶的元素就是第K大元素。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int,vector<int>,greater<int>> ans;
int i=0;
while(k--)
{
ans.push(nums[i++]);
}
while(i<nums.size())
{
if(ans.top()<nums[i])
{
ans.pop(); //将队列中最小元素pop,再插入下一个元素
ans.push(nums[i++]);
}
else
{
++i;
}
}
return ans.top();
}
};