大家好!本文会用C++模拟一个基本的priority_queue类,帮助我们更好的理解priority_queue的内置函数的实现与规则。
1. priority_queue介绍
priority_queue被叫做优先队列:
- 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
- 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素),其数据结构类似于大堆。
- 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
- 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
- empty():检测容器是否为空
- size():返回容器中有效元素个数
- front():返回容器中第一个元素的引用
- push_back():在容器尾部插入元素
- pop_back():删除容器尾部元素
- 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
- 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
priority_queue数据结构:
2. priority_queue模拟实现
我们先来看看priority_queue需要实现哪些函数:
template <class T,class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
//构造函数
priority_queue() = default;
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last);
//容器操作
void adjust_up(int child);
void adjust_down(int parent);
bool empty() const;
size_t size() const;
const T& top() const;
void push(const T& x);
void pop();
private:
Container _c;
Compare comp;
};
}
priority_queue需要实现的函数很少,而且函数也多为复用 Container类型的内置函数,所以这里不多讲解,直接贴代码。(priority_queue的重点不在它的函数,在本篇的第三个专题)。
构造函数:
//强制编译器生成默认构造函数
priority_queue() = default;
//支持迭代器构造
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_c.push_back(*first);
first++;
};
for (int i = ((_c.size() - 1 - 1) / 2); i > 0; i--)
{
adjust_down(i);
};
};
容器操作函数:
//堆的向上调整函数
void adjust_up(int child)
{
while(child > 0)
{
int parent = (child - 1) / 2;
if (comp(_c[parent], _c[child]))
{
std::swap(_c[parent], _c[child]);
child = parent;
}
else{
break;
}
}
};
//堆的向下调整函数
void adjust_down(int parent)
{
while (parent > ((_c.size()-1-1)/2))
{
int child = parent * 2 + 1;
if (comp(_c[child + 1],_c[child])&& child + 1 < _c.size())
{
child++;
}
if (comp(_c[parent],_c[child]))
{
std::swap(_c[parent], _c[child]);
parent = child;
}
else
{
break;
}
}
};
//复用_c的内置empty函数
bool empty() const
{
return _c.empty();
};
//复用_c的内置size函数
size_t size() const
{
return _c.size();
};
//复用_c的内置top函数
const T& top() const
{
return _c.front();
};
//推入一个数,再对其向上调整
void push(const T& x)
{
_c.push_back(x);
adjust_up(_c.size() - 1);
};
//交换头尾元素(头为最大元素)
//对新头元素向下调整
//对新尾元素pop
void pop()
{
if (_c.empty()) return;
std::swap(_c[0],_c[_c.size() - 1]);
_c.pop_back();
adjust_down(0);
};
3. 适配器与虚函数
在上文我们注意到,priority_queue有两个特别模板参数,是模板的第二个和第三个参数,而且,他们是priority_queue两个参数的类型,那么,他们是什么🤔?
template <class T,class Container = vector<T>, class Compare = less<T>>
{
//...
private:
Container _c;
Compare comp;
}
我们一个一个说。
第二个模板参数-适配器
:在C++中,Container叫做 适配器,它的缺省值是一个模板容器,在 priority_queue中,他的默认适配器就是vector。在整个 priority_queue的模拟实现中,所有的函数都是围绕这个适配器容器进行的,可以看作把vector的数据结构量身定做成堆的数据结构,本身堆就是可以用数组实现的。
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。在priority_queue里,当然可以传入自己设计的接口容器。
第二个模板参数-虚函数
:在C++中,有一种模板类当做模板参数传入,像函数一样被使用,这样的模板类被称为虚函数,它实际上是类而不是函数。
当然上文留给个悬念,就是没有去实现这个less类。虚函数的实现有一定的规则,现在我们来实现less类:
template<class T>
class less
{
public:
//类中只有一个()的运算符重载,这个函数用来做判断
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
虚函数的实现大概就是这样的模式,然后这个类就可以像函数一样使用。
因为priority_queue默认是大堆,所以默认判断规则为less类,当然,如果要实现小堆也是可以的,就要实现一个greater类,并像参数一样传入:
//greater类
template<class T>
class greater
{
public:
bool operator()(const T& a, const T& b)
{
return a < b;
}
};
//声明
priority_queue<int,vector<int>,greater<int>> x;
由此全部完成,附上总代码图:
#pragma once
namespace bit
{
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 Compare = less<T>>
class priority_queue
{
public:
priority_queue() = default;
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_c.push_back(*first);
first++;
};
for (int i = ((_c.size() - 1 - 1) / 2); i > 0; i--)
{
adjust_down(i);
};
};
bool empty() const
{
return _c.empty();
};
size_t size() const
{
return _c.size();
};
const T& top() const
{
return _c.front();
};
void push(const T& x)
{
_c.push_back(x);
adjust_up(_c.size() - 1);
};
void pop()
{
if (_c.empty()) return;
std::swap(_c[0],_c[_c.size() - 1]);
_c.pop_back();
adjust_down(0);
};
void adjust_up(int child)
{
while(child > 0)
{
int parent = (child - 1) / 2;
if (comp(_c[parent], _c[child]))
{
std::swap(_c[parent], _c[child]);
child = parent;
}
else{
break;
}
}
};
void adjust_down(int parent)
{
while (parent > ((_c.size()-1-1)/2))
{
int child = parent * 2 + 1;
if (comp(_c[child + 1],_c[child])&& child + 1 < _c.size())
{
child++;
}
if (comp(_c[parent],_c[child]))
{
std::swap(_c[parent], _c[child]);
parent = child;
}
else
{
break;
}
}
};
private:
Container _c;
Compare comp;
};
}
本文就到这里,感谢你看到这里❤️❤️!
我知道一些人看文章喜欢静静看,不评论🤔,但是他会点赞😍,这样的人,帅气低调有内涵😎,美丽大方很优雅😊,明人不说暗话,要你手上的一个点赞😘!