对于排序以及优先队列回调函数cmp的探究

对于排序以及优先队列回调函数的探究

1. sort的cmp参数

sort函数的原型为

template<class _RanIt,
	class _Pr> inline
	void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred)
	{	// order [_First, _Last), using _Pred
	_Adl_verify_range(_First, _Last);
	const auto _UFirst = _Get_unwrapped(_First);
	const auto _ULast = _Get_unwrapped(_Last);
	_Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _Pass_fn(_Pred));
	}

sort函数cmp参数的使用

vector<int> v{ 1,5,6,9,3,2 };
auto cmp = [](const int& a, const int& b) {
	return a < b;
};
sort(v.begin(), v.end(), cmp);

sort函数cmp的比较规则

还是对于上述的代码

vector<int> v{ 1,5,6,9,3,2};
auto cmp = [](const int& a, const int& b) {
	return a < b;
};
sort(v.begin(), v.end(), cmp);

for (int e : v) {
	cout << e << " ";
}

// 结果为:
// 1 2 3 5 6 9
  1. 而当cmp函数为return a > b时,输出为9 6 5 3 2 1
  2. 可见当cmp函数中为小于号时,为从小到大排序,为大于号时,为从大到小排序;
  3. 至于其他的排序规则,比如按照绝对值、平方等等,原理都是类似的;当参数a和b相比时,a相对于b的排序规则(大小等等),就是整个序列的排序规则

做一个检验,想让绝对值小的排在前面

vector<int> v{ -1,5,-6,9,-3,2};
auto cmp = [](const int& a, const int& b) {
	return abs(a) < abs(b);
};
sort(v.begin(), v.end(), cmp);
for (int e : v) {
	cout << e << " ";
}

// 结果为:
// -1 2 -3 5 -6 9

这里再顺带插一嘴C++sort函数原理:先使用快排,当分割的次数(递归层数)过多时,采用堆排序;若分割次数很少,则采用插入排序即可。

2. priority_queue的cmp模板参数

priority_queue的模板原型

// CLASS TEMPLATE priority_queue
template<class _Ty,
	class _Container = vector<_Ty>,
	class _Pr = less<typename _Container::value_type> >
	class priority_queue
	{	// priority queue implemented with a _Container
public:
	typedef _Container container_type;
	typedef _Pr value_compare;
	typedef typename _Container::value_type value_type;
    
    /*
        ...
    */
};

priority_queue模板的使用

using P = pair<string, int>;

struct cmp{
    bool operator()(const P& p1, const P& p2) {
        if(p1.second == p2.second) return p1.first > p2.first;
        return p1.second < p2.second;
    }
};

priority_queue<P, vector<P>, cmp> q;

// 或者

auto cmp = [](const P& p1, const P& p2){
    if(p1.second == p2.second) return p1.first > p2.first;
    return p1.second < p2.second;
};

priority_queue<P, vector<P>, decltype(cmp)> q(cmp);

priority_queue中cmp的比较规则

若熟悉优先队列的原理,不难想到,优先队列的排序或者比较不像sort一样(前后互换位置),而是节点的上浮下沉。所以类似于sort中cmp的比较规则:当cmp的参数a和b相比时,a相对于b的排序规则的节点会下沉,例如:

vector<int> v{ 5, 6, 9, 3, 8, 2 };
auto cmp = [](const int& a, const int& b) {
	return a < b;
};
priority_queue<int, vector<int>, decltype(cmp)> q(v.begin(), v.end(), cmp);
while (!q.empty()) {
	cout << q.top() << " ";
	q.pop();
}

// 结果为:
// 9 8 6 5 3 2

可以看出,cmp函数中为a < b,即值小的节点会下沉,所以整个优先队列是一个大顶堆

priority_queue的练习题

题目传送门

image-20210330101021449

  • 解题思路 – 1

    • 题目要求频率高的词在前面,相同词频的,字典序小的在前面
    • 若使用优先队列来做,很容易想到,应该对词频建立最大堆,对字典序建立最小堆
  • 代码

class Solution {
public:
    using P = pair<string, int>;
    struct cmp{
        bool operator()(const P& p1, const P& p2) {
            // 字典序最小堆
            if(p1.second == p2.second) return p1.first > p2.first;
            // 词频最大堆
            return p1.second < p2.second;
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        // 使用 map 来统计词频
        map<string, int> mp;
        for(string& word : words){
            mp[word]++;
        }
        // 将所有的 pair压入队列
        priority_queue<P, vector<P>, cmp> q{mp.begin(), mp.end()};
        vector<string> ret;
        // 取前 k 个高频词
        while(k){
            ret.push_back(q.top().first);
            q.pop();
            --k;
        }
        return ret;
    }
};

当前代码的空间复杂度为:O(N);时间复杂度为:O(NlogN) 【实际是要小于该结果的,应该为O(log(N!))】

  • 解题思路 – 2

    • 上述代码由于使用了最大堆(词频),需要维护所有压入队列的元素,时间复杂度可能偏高;故我们也可以使用 最小堆(词频),只需要维护前K个元素,复杂度降低
  • 代码

class Solution {
public:
    using P = pair<string, int>;
    struct cmp{
        bool operator()(const P& p1, const P& p2) {
            // 字典序最大堆 
            if(p1.second == p2.second) return p1.first < p2.first;
            // 词频最小堆
            return p1.second > p2.second;
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        // 使用 map 统计词频
        map<string, int> mp;
        for(string& word : words){
            mp[word]++;
        }
        priority_queue<P, vector<P>, cmp> q;
        // 所有元素压入队列
        for(const auto& e : mp){
            q.push(e);
            // 但只维护前 K 个元素,因为堆顶元素词频小且字典序大
            if(q.size() > k) q.pop();
        }
        vector<string> ret;
        while(!q.empty()){
            ret.push_back(q.top().first);
            q.pop();
        }
        // 需要将结果反转
        reverse(ret.begin(), ret.end());
        return ret;
    }
};

当前代码的空间复杂度:O(N),时间复杂度:O(NlogK)【实际也要小于该结果】

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值