双端队列是什么?
首先我们来看一下库中的双端队列能够调用的接口有哪些
可以看到它的成员函数就只有上面的几个,如果学过数据结构那么双端队列(priority_queue)其实就是一个堆,如果你没有学过双端队列,那么下面我们先来使用以下库中给我们的双端队列是怎么样运行的。
可以看到我们传到dp中的数字按照从大到小的优先级顺序被从双端队列中删除了。也就是每一次都取堆顶的元素,然后删除堆顶的元素。
那么能不能让顺序变成 1 3 5 6呢?肯定是可以的。
那么这是为什么呢?待会我们就会知道了。
模拟实现双端队列(priority_queue)
既然双端队列在使用上就是一个堆,那么要模拟实现双端队列,那么底层容器,自然就需要使用vector(或者使用deque也可以,能支持下标访问)。
那么首先让我们来看一看我们要是实现的这个双端队列的基本结构。
下面来完成成员函数的实现
默认构造和迭代器区间初始化
namespace LHY
{
#include
template,class Compare = less>
class priority_queue
{
priority_queue()
{}//即使你什么也不写,对于内置的自定义成员,编译器会自动的调用自定义成员的默认构造函数
template
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
for (int i = (_con.size() - 2) / 2; i >= 0; --i)
{
adjustdown(i);
}
}//这里是使用了初始化队列来调用_con的迭代器区间初始化的方式
private:
Container _con;
Compare _com;
};
}
这里需要记住的知识点为:如果成员为自定义类型,那么编译器会帮我们调用自定义类型的默认构造函数,析构也是一样的,可以使用初始化列表来调用成员中的特定初始化函数。以及对于模板函数不需要显示实例化,因为编译服务器托管网器可以通过传入的形参来推断类型,但是模板类就不可以。
push函数
push函数的功能肯定就是将一个值放到双端队列中,那么既然双端队列本质是一个大堆,那么往堆中放数据,其实也就是将数据放到vector的最后一位然后,使用向上调整算法调整一次即可。
向上调整算法:
void adjust服务器托管网up(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[child]>_con[parent])
{
swap(_con[child], _con[parent]);
child = parent;
parent = parent - 1 / 2;
}
else
{
break;
}
}
}//这里建立的是大堆
向下调整算法:
void adjustdown(int parent)
{
int child = 2 * parent + 1;
while (child _con[child])
{
child++;
}//选择左右孩子中大的那一个
if (_con[child] > _con[parent])
{
swap(_con[child], _con[parent]);//交换孩子和父亲节点
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
如果你对这两个算法的感到有不理解的地方,请看我的这篇博客:https://blog.51cto.com/u_15838996/6327521
那么有了这两个函数之后就可以来进行push函数的编写了。
void push(const T& x)
{
_con.push_bck(x);
adjustup(_con.size()-1);
}
重点知识:回顾向上和向下调整算法,堆插入数据的方法。
pop函数
既然双端队列是按照优先级顺序删除元素的,那么要删除元素肯定是要将顶部得元素先和最后的元素交换,交换之后让vector删除最后一个元素,在使用一次向下调整算法重新建堆即可。
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjustdown(0);
}
重点知识:理解堆是如何删除数据的
top函数
这个函数就很简单了直接返回vevtor中位置为0的数据即可
const T& top()
{
return _con[0];
}
size函数和判空函数
这两个函数更加的简单,只需要返回vector中的size函数,以及调用vector的empty函数就可以了
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.szie();
}
经过上面的步骤我们就模拟实现了一个属于我们的一个不太完善的双端队列。让我们来调用尝试一下。
可以看到成功了,但是如果我是想要建立一个小堆呢?如果没有仿函数的话,要做到这一点只有一个方法,那就是修改向上和向下调整的代码,但是这显然不是一个好方法。所以为了让我们能够和系统调用一样,只用修改模板实例化的参数就能够实现将建立大堆变成建立小堆。就让我们来实现和学习一下仿函数的用法。
学习使用仿函数
首先我们要知道仿函数的本质不是一个函数,而是一个类。下面我们来实现一个最简单的仿函数。
一个比较大小的仿函数。
class My_Compare
{
public:
bool operator()(int x, int y)
{
return x > y;
}//在这个类里面重载()操作符
};
void testmy_com()
{
My_Compare com;
cout
上面就是创建一个类然后使用这个类
然后让我们看看调用的结果:
那么由此就完成了一个很简单的仿函数,但是这个仿函数却只能比较int类型的,那么能不能加入模板呢?答案当然是可以
template
class My_Compare
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
这样我们在使用一下这个仿函数
只需要修改一下实例化的参数,就可以比较double类型
使用这个仿函数就能够实现由用户决定是建立大堆还是建立小堆
下面就来看一看仿函数可以修改哪些地方。
void adjustup(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[child] > _con[parent])//下面的功能和上面这个是一样的。
if(_com(_con[child],_con[parent]))
//把这里的比较换成了使用实例化的一个仿函数比较
{
swap(_con[child], _con[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
void adjustdown(int parent)
{
int child = 2 * parent + 1;
while (child
因为我在实现的时候,如果用户不传入额外的显示实例化,那么就会采用默认的less类,那么最后建立的就是大堆,如果用户最后显示实例化的时候写了我所定义的第二个greater函数,那么优先级顺序就会被修改。
那么我们能否传入我们自己写的仿函数呢?当然可以我们来看一下下面的这个例子:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout , greater> q2;
q2.push(new Date(2018, 10, 29));
q2.push(new Date(2018, 10, 28));
q2.push(new Date(2018, 10, 30));
cout
可以看到在将一个日期类放到双端队列中后,最后返回的是日期最大的那个对象。因为在日期类中定义了日期对象的大小比较。
但是如果我是将一个日期类的地址放到双端队列中呢?也就是将上面注释的代码解注释。
可以看到这里就出现了问题,那是为什么呢?因为我们在我们的命名空间中所写的那个仿函数是不具有将指针解引用然后比较的功能的,所以这里就是按照三个对象new出来的空间所在的序列大小进行的比较,所以会出现随机的答案。如何结局呢?
很简单就是我们自己写一个能够解引用比较的仿函数。
class My_com
{
public:
bool operator()(Date* x, Date* y)
{
return (*x) > (*y);
}
};
然后再下面将这个仿函数显示实例化
void TestPriorityQueue()
{
// 大堆,需要用户在自定义类型中提供 q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout , My_com> q2;
q2.push(new Date(2018, 10, 29));
q2.push(new Date(2018, 10, 28));
q2.push(new Date(2018, 10, 30));
cout
这样运行出来的代码就不会出现任何的错误了
完整的双端队列代码
namespace LHY
{
template
class less
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template
class greater
{
public:
bool operator()(const T& x, const T& y)
{
return x , class Compare = less>
class priority_queue
{
public:
void adjustup(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[child] > _con[parent])
if(_com(_con[child],_con[parent]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
void adjustdown(int parent)
{
int child = 2 * parent + 1;
while (child
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
for (int i = (_con.size() - 2) / 2; i >= 0; --i)
{
adjustdown(i);
}
}//这里是使用了初始化队列来调用_con的迭代器区间初始化的方式
private:
Container _con;
Compare _com;//这里就是示例化了我们内部所写的less
};
}
希望这篇博客能对你有所帮助,如果发现错误,欢迎指正。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 邀请函|天嵌邀您共聚2023北京数字交通大会暨博览会
2023北京数字交通大会暨博览会 2023.09.11服务器托管网-09.13 中国国际展览中心(新馆) 天嵌展位:W2A69 天嵌科技诚邀您的莅临!服务器托管,北京服务器托管,服务器租用 http://www.fw服务器托管网qtg.net 机房租用,北京机…