距离有了leetcode排名已经过去了一个多月,这一个多月里我又做了一百道题,排名变成了一个月前的1/2,每天吃完早饭(没错,终于不再是半夜睡觉中午起床了)之后的第一件事就是上号做几道题。然而依旧觉得自己的水平没怎么提升……
于是想多做一些困难题,赫赫,困难题果真是很困难呢!其中很多题都涉及到了优先队列(priority queue),所以本篇博客的内容就是优先队列。
优先队列是一种特殊的队列。队列是遵循先进先出原则的,而优先队列中元素的出队顺序是按照优先级排列(比如升序或者降序)。优先队列是由堆来实现的,本质上是一棵完全二叉树。在C++里使用优先队列需要#include<queue>
。
优先队列的定义
大顶堆:priority_queue<int> queue;
这是最简单的一种队列的定义,表示值越大的元素优先级越高。如果想要实现小顶堆(值越小优先级越大),那么可以这样定义:
priority_queue<int,vector<int>,greater<int>> queue;
其中第一个参数为元素的类型,第二个参数为存放元素的容器类型(必须是数组类型的容器,比如vector,不可以用list),第三个参数为比较函数。元素类型和比较函数可以自己定义。需要注意的是,如果元素类型是自定义的,那么后面的两个参数也必须自定义好,不然C++不知道以什么方式来排序。以下是我从某个网站上找来的一个自定义比较函数示例:
class mycomparison
{
bool reverse;
public:
mycomparison(const bool& revparam=false)
reverse=revparam;
bool operator() (const int& lhs, const int&rhs)
const{
if (reverse)
return (lhs>rhs); //小顶堆
else
return (lhs<rhs); //大顶堆
}
};
//定义优先队列
priority_queue<int,vector<int>,mycomparison> q1; //大顶堆
priority_queue<int,vector<int>,mycomparison> q2(mycomparison(true)); //小顶堆
上面这段函数可以通过改变reverse
的值来改变元素的排列方式。需要注意的是,优先队列中用到的进行比较的操作符是”<”,因此在自定义比较函数的时候,可以写成bool operator()
或bool operator<
,但是不可以写成bool operator>
。
优先队列的操作
优先队列的常用函数包括:
queue.size(); //返回元素的数量
queue.push(x); //存入一个元素
queue.pop(); //删除顶端元素
queue.top(); //返回顶端元素
queue.empty(); //返回一个布尔量,表示队列是否为空
一个例子
以LeetCode第23题,合并k个升序列表为例。该题的输入是k个列表,每个列表内的节点已经按照升序排序,需要将这k个列表合并为一个列表,按照升序排列并输出。这是一道困难题。
解题思路是,首先将每个列表的头节点存入优先队列(按照升序排列),每次取出值最小的那个节点,并到新的列表里,然后将该节点的下一个节点(如果有的话)存入优先队列,一直持续到优先队列中所有的元素都被取完。以下是我写的代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*, vector<ListNode*>, func> queue;
for(auto &it:lists)
{
//获取头节点
if(it)
queue.push(it);
}
ListNode* head = new ListNode(0);
ListNode* rtn = head;
while(!queue.empty())
{
ListNode* tmp = queue.top();
head->next = tmp;
head = head->next;
queue.pop();
if(tmp->next) //这个列表还没有遍历完,将其下一个元素存进来
{
queue.push(tmp->next);
}
}
return rtn->next;
}
struct func
{
bool operator() (ListNode* a, ListNode* b)
{
return a->val > b->val; //小顶堆
}
};
};
2021.12.25补充
补充leetcode第451题。题目的内容是给定一个字符串,将其中的字符按照出现频率进行降序排列并输出。例如,如果输入的字符串是”tree”,那么输出应该是”eetr”或”eert”。
这道题可以采用哈希表+优先队列来解。首先遍历字符串将字符存入哈希表统计其出现的次数,然后再使用优先队列(大顶堆)根据出现频率对字符进行排序,最后取出优先队列中的字符输出。
光靠文字有点解释不清楚,以下是C++代码:
class Solution {
public:
string frequencySort(string s) {
//hashmap+priority_queue
int n = s.size();
unordered_map<char,int> map;
priority_queue<pair<char,int>,vector<pair<char,int>>,func> q;
for(char it: s)
{
map[it]++; //对每个字符出现的频率进行统计
}
for(auto& it:map)
{
q.push(it);//存入大顶堆
}
string rtn;
while(!q.empty())
{
char c = q.top().first;
int i = q.top().second;
for(int j = 0; j < i; j++)
{
rtn +=c; //按照顺序取出
}
q.pop();
}
return rtn;
}
//大顶堆
struct func
{
bool operator() (pair<char,int> a, pair<char,int> b)
{
return a.second < b.second;
}
};
};