🌇个人主页:_麦麦_
📚今日名言:每想你一次,天上飘落一粒沙,从此形成了撒哈拉。每想你一次,天上就掉下一滴水,于是形成了太平洋。——三毛
目录
一、前言
今天为大家带来list的介绍和模拟实现,文章若有不足之处,欢迎大家给出指正!
二、优先级队列的介绍和接口
2.1优先级队列的介绍
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元 素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭 代器访问,并支持以下操作:
● empty():检测容器是否为空
● size():返回容器中有效元素个数
● front():返回容器中第一个元素的引用
● push_back():在容器尾部插入元素
● pop_back():删除容器尾部元素
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指 定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap和pop_heap来自动完成此操作。
2.2 优先级队列的接口
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意: 默认情况下priority_queue是大堆。
函数说明 | 接口说明 |
priority_queue()/priority_queue(first,last) | 构造一个优先级队列 |
empty() | 检测优先级队列是否为空,是则返回ture,否则返回false |
top() | 返回优先级队列中最大(最小元素),即堆顶元素 |
push(x) | 在优先级队列中插入元素x |
pop | 删除优先级队列中最大(最小)元素,即堆顶元素 |
三、优先级队列的模拟实现
3.1模版参数与类成员
在进行完优先级队列的介绍和接口讲解后,我们就来进行优先级队列的模拟实现。
在进行一个类的模拟实现之前我们先来看看它的模版参数和成员变量。我们要知道的是相比于之前的string、vector和list来言,优先级队列是采取容器适配器的方式,也就是它并没有采取新的容器存储的方式,而是对之前的容器进行封装,由此它的模版参数也与之前的有所不同。它的模版参数共有三个,分别是存储数据的类型,存储数据的容器,数据比较的类。它的类成员共有两个,分别是存储的容器和数据比较的类,对他们进行实例化。
具体代码如下:
namespace mine
{
template<class T,class Container=vector<T>,class Compare=less<T>>
class priority_queue
{
private:
Container con;
Compare comp;
};
}
3.2 优先级队列的构造函数
在本文中我们实现两种优先级队列的构造函数,分别是无参的和用一段迭代器区间来构造。
对于无参的构造函数,我们无需做任何处理,这是因为我们的类成员都是自定义类型,对于这种类型,编译器会调用它们自己的默认构造函数。
对于迭代器区间的构造函数,与我们之前的容器所写的会有所不同。因为优先级队列使以容器适配器的方式实现的,因此在实际使用当中,我们也不知道会使用什么类型的迭代器,因此对于采取迭代器区间的构造函数就需要模版参数来根据迭代器的类型进行重载。然后呢就是大家所习以为常的数据插入,由于我们所学的容器大多实现了尾插的接口,因此我们只需要对迭代器进行解引用并调用容器的尾插接口就可以将数据插入,然后再对迭代器进行++就可以不断地将数据进行插入了。最后就是对数据的调整了,我们知道,优先级队列默认是大堆的形式,因此我们还需要对容器内的数据进行向下调整,使父节点大于等于子节点。
注:在向下调整的过程中,我们只需要从倒数第二层开始即可,因为最后一层由于是叶子结点就无需进行调整了。
具体代码如下:
//无参的构造
priority_queue(){}
//迭代器区间的构造
void Adjust_down(int parent)
{
int child = 2 * parent + 1;
while (child<con.size())
{
//找到左右大的孩子
if (comp(con[child],con[child+1])) child += 1;
//进行调整
if (comp(con[parent], con[child])) swap(con[parent], con[child]);
parent = child;
child = 2 * parent + 1;
}
}
template<class Inputiterator>
priority_queue(Inputiterator first, Inputiterator last)
{
//插入数据
auto it = first;
while (it != last)
{
con.push_back(*it);
++it;
}
//向下调整
for (int i = (con.size()-2)/2; i >= 0; --i)
{
Adjust_down(i);
}
}
3.3 优先级队列的插入与删除
在构造优先级队列成功后,我们不可避免地会对其进行数据的插入和删除。
那么优先级队列该如何进行数据的插入呢?首先第一步就是调用容器尾插的接口将数据插入,可是数据插入之后并不一定符合我们大堆的需求,因此我们还需要对插入的数据进行向上调整到合适的位置。也就是将其与父节点进行比较,如果大于父节点就进行数据的交换,然后再与新的父节点进行比较,直至小于等于上一个父节点或是已经到达了堆顶。
那么数据的删除呢,由于优先级队列的删除是对堆顶的元素进行删除,那么有的小伙伴可能首先想到的就是对容器进行头删的操作,但是并不是所有的容器都有头删的操作,就比如说vector,其次vector的头删的代价也比较大,需要将数据一个个向前挪动,于是比较好的方式就是将堆顶元素与最后一个元素进行交换,然后利用尾删的接口将“堆顶元素”删除,然后再重新进行一遍向下调整的方式将堆中数据进行排序。
具体实现代码如下:
//向上调整
void Adjust_up(int child)
{
int parent = (child - 1) / 2;
while (child!=0)
{
//if (con[child] > con[parent])
if (comp(con[parent], con[child]))
{
swap(con[parent], con[child]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
//数据的插入
void push(T val)
{
con.push_back(val);
Adjust_up(con.size()-1);
}
//数据的删除
void pop()
{
swap(con[0], con[con.size() - 1]);
con.pop_back();
//向下调整
for (int i = (con.size() - 2) / 2; i >= 0; --i)
{
Adjust_down(i);
}
}
3.4 优先级队列的其他接口
在模拟实现优先级队列的插入和删除后,其他的接口就比较简单了,诸如返回堆顶元素,返回元素个数等等……
//返回堆顶元素
const T& top() const
{
return con[0];
}
//判断堆是否为空
bool empty() const
{
return con.empty();
}
//返回堆中有效元素的个数
size_t size() const
{
return con.size();
}
四、全部代码
#pragma once
namespace mine
{
template<class T,class Container=vector<T>,class Compare=less<T>>
class priority_queue
{
public:
priority_queue(){}
void Adjust_down(int parent)
{
int child = 2 * parent + 1;
while (child<con.size())
{
//找到左右大的孩子
if (comp(con[child],con[child+1])) child += 1;
//if (child + 1 < con.size() && con[child + 1] > con[child]) child +=1;
//进行调整
if (comp(con[parent], con[child])) swap(con[parent], con[child]);
//if (con[child] > con[parent]) swap(con[parent], con[child]);
parent = child;
child = 2 * parent + 1;
}
}
void Adjust_up(int child)
{
int parent = (child - 1) / 2;
while (child!=0)
{
//if (con[child] > con[parent])
if (comp(con[parent], con[child]))
{
swap(con[parent], con[child]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
template<class Inputiterator>
priority_queue(Inputiterator first, Inputiterator last)
{
//插入数据
auto it = first;
while (it != last)
{
con.push_back(*it);
++it;
}
//向下调整
for (int i = (con.size()-2)/2; i >= 0; --i)
{
Adjust_down(i);
}
}
void pop()
{
swap(con[0], con[con.size() - 1]);
con.pop_back();
//向下调整
for (int i = (con.size() - 2) / 2; i >= 0; --i)
{
Adjust_down(i);
}
}
void push(T val)
{
con.push_back(val);
Adjust_up(con.size()-1);
}
const T& top() const
{
return con[0];
}
bool empty() const
{
return con.empty();
}
size_t size() const
{
return con.size();
}
private:
Container con;
Compare comp;
};
}
五、结语
到此为止,关于优先级队列的讲解就告一段落了,至于其他的内容,小伙伴们敬请期待呀!
关注我 _麦麦_分享更多干货:_麦麦_-CSDN博客
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!