优先队列是一种抽线数据类型,优先队列中的每个元素都有优先级,优先级高的先出队,而优先级相同的则按照入队的顺序出队。优先队列往往通过二叉堆来实现。
二叉堆的性质
二叉堆通常使用数组来表示,下图展示了如何使用数组来表示二叉堆

根据根节点元素的值小于或者大于左右节点的值,可以分为最小堆(小顶堆)或者最大堆(大顶堆),主要区别在于堆顶是最小值还是最大值。
优先队列存在从堆尾插入元素和删除堆顶元素两种操作方法,因此具体的类实现如下:
class MaxPQ
{
// 成员变量
vector pq; // 用于存储大顶堆的元素
int N = 0; // 当前堆包含的元素个数
public:
MaxPQ(int cap)
{
// 开辟空间
pq.resize(cap + 1); // 序号为0不存储元素
}
// 返回当前队列中最大元素
int max()
{
return pq[1];
}
// 插入元素e,即将元素e添加到队尾,然后上浮到合适的位置
void insert(int e);
// 删除并返回当前队列中最大元素
int delMax();
// 上浮第k个元素,以维护最大堆的性质
void swim(int k);
// 下沉第k个元素,以维护最大堆性质
void sink(int k);
// 交换数组的两个元素
void exch(int i, int j)
{
int temp = pq[j];
pq[j] = pq[i];
pq[i] = temp;
}
// 判断pq[i] 是否比pq[j]小
bool less(int i, int j)
{
return pq[i] < pq[j] ? true : false;
}
// 返回当前节点父节点索引
int parent(int root)
{
return root / 2;
}
// 返回左子节点的索引
int left(int root)
{
return 2 * root;
}
int right(int root)
{
return 2 * root + 1;
}
};
从子节点的角度来看,如果某个节点A比他的父节点B大,那么A不应该在子节点,而应该把A节点上浮把父节点B换下来,自己做父节点。
void MaxPQ::swim(int k)
{
// 给的节点与父节点相比
while(k > 1 && less(parent(k), k))
{
// 交换节点
exch(parent(k), k);
k = parent(k);
}
}
从父节点的角度来看,如果父节点A与其两个子节点比较大小,如果A不是最大的就需要调整位置,进行下沉把较大的子节点和A交换。
void MaxPQ::sink(int k)
{
// 如果沉到堆底,就不下沉了
while(left(k) <= N)
{
// 先假设左边节点较大,返回左边节点的索引
int maxer = left(k);
// 如果右边节点存在,比一下大小
if(right(k) <= N && less(maxer, right(k))
{
maxer = right(k);
}
// 如果节点k比两个孩子都大,就不必下称了
if(less(maxer, k)) break;
exch(k, maxer);
k = maxer;
}
}
思路:从队尾插入节点N,然后将节点N上浮到合适的位置。
void MaxPQ::insert(int e)
{
N++;
// 把先添加的元素查到最后
pq[N] = e;
// 让它上浮到正确的位置
swim(N);
}
思路:先将堆顶元素A和堆底元素B进行交换,然后删除元素A,最后让元素B下沉到合适的位置
int MaxPQ::delMax()
{
int max = pq[1];
// 交换
exch(1, N);
pq[N] = NULL;
N--;
// 让B下沉
sink(1);
return max;
}
#include
#include
using namespace std;
class MaxPQ
{
private:
// 存储元素的数组
vector pq;
// 当前PQ中的元素个数
int N ;
public:
// 构造函数
MaxPQ(int capacity)
{
// 索引0不用,所以需要多分配一个空间
pq.resize(capacity + 1);
N = 0;
}
// 返回当前队列中最大元素
int max()
{
return pq[1];
}
// 插入元素
void insert(int e)
{
// 将要插入的元素添加到堆底的位置,然后上浮到正确位置
++N;
// 先将新的元素添加到最后
pq[N] = e;
// 然后上浮到正确位置
swim(N);
}
// 删除并返回当前数组中最大元素
int delMax()
{
// 先将堆顶的元素A和堆底元素B位置对调,然后删除A,将B下沉到合适位置
int max = pq[1];
// 兑换
exch(1, N);
// 删除
--N;
// 下称
sink(1);
return max;
}
/* ******************* helper functions ********************* */
int parent(int root)
{
return root / 2;
}
int left(int root)
{
return root * 2;
}
int right(int root)
{
return root * 2 + 1;
}
// 上浮第k个元素,以维护最大堆性质
void swim(int k) // 定位为子节点
{
// 如果某个节点A比它的父节点大,那么对A上浮
while(k > 1 && less(parent(k), k)) // k> 1才有父节点
{
cout << "swin: " << pq[k] << endl;
// 交换序号为parent(k) 和 k 的值
exch(parent(k), k);
// 交换完后k位于父节点处
k = parent(k);
}
}
// 下沉第k个元素
void sink(int k) // 定位为父节点
{
// 如果某个节点A比它的子节点小,则对A下沉
// 需要判断A与其两个子节点比较大小,判断A是否为最大值
while(left(k) <= N)
{
// 先假设左节点为最大值
int older = left(k);
// 如果右节点存在,比较大小
while(right(k) <= N && less(left(k), right(k)))
{
// 最大值为右节点
older = right(k);
}
// 父节点与最大子节点进行比较
if(less(older, k)) break;
// 否则交换并下沉k节点
cout << "sink: " << pq[k] << endl;
exch(older, k);
k = older;
}
}
// 交换数组的两个元素
void exch(int i, int j)
{
// cout << "i: " << i << " j: " << j << endl;
int temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
}
// 比较pq[i]是否小于pq[j]
bool less(int i, int j)
{
return pq[i] < pq[j];
}
};
int main()
{
MaxPQ data(10);
data.insert(2);
data.insert(5);
data.insert(6);
data.insert(10);
data.delMax();
cout << data.max() << endl;
return 0;
}
priority_queue在C++的queue库中实现,优先队列的底层实现是heap堆,可以在任意时刻从堆底插入元素,但是只可以访问堆顶元素。提供了如下一些成员函数:
template ,
class Compare = less > class priority_queue;
与stack和queue类似,priority_queue也是container adaptor,设计用来保证队顶元素为最大值。
//最小堆
priority_queue ,greater > q;
//最大堆
priority_queue ,less >q;
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
#include
#include
#include
using namespace std;
int main()
{
priority_queue a; // 默认为最大堆
a.push(3);
a.push(1);
a.push(6);
while (!a.empty())
{
cout << a.top() << '\n';
a.pop();
}
}
对于用户自定义的数据结构,常见有两种比较规则来初始化priority_queue对象,一个是重载小于运算符,另一个是使用重载函数操作符的类对象。在2. 使用pair中介绍了重载()的方法,在3.使用自定义类型中介绍了小于运算符重载的方法
#include
#include
#include
using namespace std;
// 重载()符号
typedef struct
{
bool operator() (const pair& a, const pair& b)
{
// 最大堆
return a.second < b.second;
// 最小堆
// return a.second > b.second;
}
}cmp;
int main()
{
priority_queue< pair, vector>, cmp > a;
pair b(1, 2);
pair c(1, 3);
pair d(2, 5);
a.push(d);
a.push(c);
a.push(b);
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '\n';
a.pop();
}
}
#include
#include
using namespace std;
typedef struct Exp1
{
// 运算符重载
int data;
Exp1(int a) { data = a;}
bool operator<(const Exp1& a) const
{
return data < a.data; // <待参考值,比较值>
}
} EXP1;
int main()
{
Exp1 a(1);
Exp1 b(2);
Exp1 c(4);
priority_queue d;
d.push(a);
d.push(c);
d.push(b);
while(!d.empty())
{
cout << "top: " << d.top().data << " ";
d.pop();
}
cout << endl;
}