xxxx先来一个大爆炸!!
xxxx不要慌,我们慢慢解读,priority_queue(优先级队列),这确实是我们没有听说过的,不是我们在数据结构中讲过的。但是,我们注意到图中的一句话This context is similar to a heap… heap大家应该都知道,heap就是堆,堆应该大家都很熟悉吧,不熟悉请看我之前的文章《堆(Heap)的基本知识和堆排序》墙裂推荐先看懂堆是啥,再看本文,因为后文不会花过多时间在如何建堆,调整堆上
xxxx我们再来看一下上面的介绍,its first element is always the greatest of the elements it contains 它的首个元素是最大的元素。
我要说明一下,这里的最大,并不是数值最大,而是优先级最大,就是人为规定的一个秩序。任何秩序都可以。例如:数值大小,字符串长度等等人为规定的规则。不过,最常见的就是按照大小,大小优先级也是STL中提供的两种内置的规则
xxxx其实这个也是我们能理解的,堆分为大堆、小堆。堆顶元素就是所有数据中最大最小的元素。只不过在这里,将数值的大小扩展成为了优先级这就变得十分宽泛了,你可以自己定义具体的优先级是什么。
xxxx我们再注意一下,priority_queue的模板。template
1、T: 我们应该非常熟悉,就是这个容器存储的数据类型,会根据显示实例化时自动实例化成对应的容器。
2、vector< T >: 这个就说明,priority_queue底层默认就是vector,但是堆底层一般就是使用数组、vector,因此我们一般不适用其他数据结构
3、Compare = less< typename Container::value_type> 这个是我们重要介绍的,他决定了你的优先级是什么,是什么样的规则。但是,这个我先放在最后讲,作为一个重点,就是我要讲到的仿函数或者成为函数对象
我们先学会priority_queue的使用,再慢慢去学习它的底层实现
这是最简单的push、pop。当然,priority_queue还有其他接口
这些接口都是比较常规的,就不使用了,后期我会将他们模拟实现。基本就是完全封装的底层的数据结构的接口。
大纲:
template<class T, class Container = vector<T>>
class priority_queue
{
public:
void push(const T& x); //就是堆的插入数据,然后向上调整AdjustUp
void pop(); //就是堆的删除数据,将第一个元素与最后一个元素换,删除后,向下调整AdjustDown
T top() const; //return _con.front();
bool empty(); //return _con.empty();
size_t size(); //return _con.size();
private:
Container _con;
};
xxxx其实,这就跟C语言用struct借助数组完成堆数据结构没有任何本质区别,只要那里懂了,就可以轻而易举完成上述的简易版priority_queue。完成了上面的基础操作,我们就可以来进行下一阶段,就是高级版(完整版)priority_queue,在这里,我们就可以自主定义优先级的规则,那么首先,我们需要一个储备知识,就是仿函数 \ 函数对象
xxxx这是360百科给出的解释,我觉得概括的非常好。关键字就是“类、使用像函数、重载operator()、函数的行为”
xxxx接下来我就利用STL中仿函数less来讲解,啥是仿函数!
xxxx我们可以总结:less类是一种模板类,是用于比较数据是否小于的,而通过什么呢?就是通过重载operator<( )这样我们就能知道了less的功能,但是一个类为何叫仿函数,我们还是不清楚。下面我先给大家一个例子!
xxxx我们看到,这个compareLess仿佛是一个函数一样,名字后面加(),()里我们可以传入参数。但是实际上,compareLess这是一个less类的对象。那么为什么可以做到这样仿佛像是一个函数在被调用呢?那就是上面提到的,less类中重载了operator( )。
下面我们来模拟实现一下less,大家就可以理解的很透彻了!
模拟实现:
template<class T>
struct less
{
bool operator()(const T& x, const T& y)
{
return x<y;
}
};
xxxx值得注意的是,在这里,当我们调用less类对象的operator( )时,最正规的应该是compareLess.operator()(x, y) 但是经过编译器的优化后,就变成了**compareLess(x, y)**所以,他才有像函数调用一样的效果。
greater,就是less的反义。如果我们在priority_queue中把less换成greater,那么priority_queue的底层就变成了小堆。
xxxx接下来,我们就实现一下less这个函数对象,并把它加入到函数模板中即可
template<class T>
struct less
{
bool operator()(const T& x, const T& y)
{
return x<y?
}
};
template<class T, class Container = vector<T>, class Compare = less<typename Container::value_type>>
class priority_queue
{};
解释:
1、我们将Compare搞成模板,这样,就可以接受不同的优先级规则,当我们在priority_queue实现的时候,遇到优先级的比较,我们就可以用这个函数对象Compare(x,y)这样就可以进行比较。
2、为什么使用typename Container::value_type?在低版本编译器,使用T类型,或者typename Container::value_type都可以,都是代表了T的含义,都是一种数据类型,但是直接使用T是有问题的,在程序编译时,由于模板还没有实例化,我们根本就不知道T是什么,所以我们就要用Container::value_type告诉编译器这是一个数据类型,而typename就是修饰Container::value_type这是一个类型名称,好让编译器先通过编译。直接使用T是不对的,只是低版本编译器没有进行检查。
下面就是priority_queue完整的实现代码
namespace zzk
{
template<class T>
struct Less
{
bool operator()(const T& l, const T& r)
{
return l < r;
}
};
template<class T>
struct Greater
{
bool operator()(const T& l, const T& r)
{
return l > r;
}
};
//底层是堆
template<class T, class Container = vector<T>, class Compare = Greater<typename Container::value_type>> //question1
class priority_queue
{
public:
typedef typename Container::value_type CVT;//question:类模板没实例化,就不知道vector是啥,它的value_type是什么,所以用typename让他成为一种类型
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
T top() const
{
return _con.front();
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
void AdjustUp(size_t child)
{
Compare compare;
size_t parent = (child-1)>>1;
while (child > 0)
{
if (compare(_con[parent] , _con[child]))
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
break;
}
}
}
void AdjustDown(size_t parent)
{
Compare compare;
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && compare(_con[child] , _con[child + 1]))
child++;
else
{
if (compare(_con[parent],_con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
int child = parent * 2 + 1;
}
else
{
break;
}
}
}
}
private:
Container _con;
};
}
xxxxpriority_queue(优先级队列)是以堆为基础的数据结构,从堆上发展而来,基本思想就是堆的思想,不过现在我们可以自己来规定优先级的规则。STL中使用函数对象/仿函数来传入优先级,这里我们就学习到了基本的函数对象的使用,其实函数对象永不止这些内容,后期博主还会仔细谈一下函数对象和函数对象与函数指针的比较。。。敬请期待!
xxxx如果本文出现了错误或者各位读者有什么想说的,都可以在评论处指出。让我们一起学习,共同进步!