C++ set使用方面的小tip(补充有优先队列的使用,以哈夫曼树为例)

下面先给出该题目的简单介绍,偷个懒就不打字了,直接截图

下面是实现的代码:

class FM{
public:
    string food,cuisine;
    int rate;
    FM(){
        food = cuisine = "";
        rate = 0;
    }
    FM(string food,string cuisine,int rate):food(food),cuisine(cuisine),rate(rate){
        //cout<<"普通构造函数被调用了"<<endl;
    }
    FM(const FM& f):food(f.food),cuisine(f.cuisine),rate(f.rate){
        //cout<<"拷贝构造函数被调用了"<<endl;
    }
    bool operator<(const FM &other) const
    {
        if (rate == other.rate)
            return food > other.food;
        return rate < other.rate;
    }
};

class FoodRatings {
    unordered_map<string, FM> fs;
    unordered_map<string, set<FM>> cs;
public:
    FoodRatings(vector<string> &foods, vector<string> &cuisines, vector<int> &ratings) {
        for (int i = 0; i < foods.size(); ++i) {
            string f = foods[i], c = cuisines[i];
            int r = ratings[i];
            FM fm(f,c,r);
            fs[f] = fm;
            cs[c].emplace(fm);
        }
    }

    void changeRating(string food, int newRating) {
        string c = fs[food].cuisine;
        cs[c].erase(fs[food]);
        fs[food].rate = newRating;
        cs[c].emplace(fs[food]);
    }

    string highestRated(string cuisine) {
        return cs[cuisine].rbegin()->food;
    }
};

这个题目不难,中等水平,不过却包含了非常多的知识点

一. set与unordered_set的存储区别
set的底层实现是平衡二叉树,而unordered_set是哈希表,这就要求unordered_set的内容必须是可哈希的,而set则不需要;set是有序的,unordered_set是无序的;set搜索元素的复杂度是对数级别,而unordered_set是线性级别。
二. 如何利用set对自定义类型进行排序
set中的元素自动排序,这就要求不能直接改变元素的内容,因为这样会破坏排好的序,必须先删除再插入;set有内置的排序函数,但是对于自定义类型,则需要先对操作符进行重载,当然也有自定义比较函数的,不过我认为重载操作符比较直观。

class FM{
public:
    string food,cuisine;
    int rate;
    FM(){
        food = cuisine = "";
        rate = 0;
    }
    FM(string food,string cuisine,int rate):food(food),cuisine(cuisine),rate(rate){
        //cout<<"普通构造函数被调用了"<<endl;
    }
    FM(const FM& f):food(f.food),cuisine(f.cuisine),rate(f.rate){
        //cout<<"拷贝构造函数被调用了"<<endl;
    }
    bool operator<(const FM &other) const
    {
        if (rate == other.rate)
            return food > other.food;
        return rate < other.rate;
    }
};

重载操作符,参数必须为常量引用,否则会报错。另外自定义数据类型要实现普通构造函数和拷贝构造函数,拷贝构造函数的参数也必须是常量引用,可以使用初始化列表进行初始化。
三. 什么情况下会调用拷贝构造函数
1.显式地以一个类对象作为另一个类对象的初值
2.当类对象被作为参数交给函数时
3.当函数返回一个类对象时
对于本题

fs[f] = fm;//没有调用拷贝构造函数
cs[c].insert(fm);//调用了拷贝构造函数

set调用insert语句时调用了FM类的拷贝构造函数,哈希表fs没有调用任何构造函数,只是做了关联。
四. begin()和end()
对于set而言,我们可以用begin()或rbegin()来取得排序的第一个元素和最后一个元素,但不能使用end(),因为end()指向的是最后一个元素的下一个元素,其实也就是空。

平时写文章比较少,知识层次也有限,如有不妥欢迎大家批评指正,我们共同进步。

下面把使用优先队列(默认也就是大顶堆)的解法也贴出来,优先队列的自定义排序

class FM{
public:
    string food,cuisine;
    int rate;
    FM(){
        food = cuisine = "";
        rate = 0;
    }
    FM(string food,string cuisine,int rate):food(food),cuisine(cuisine),rate(rate){
        //cout<<"普通构造函数被调用了"<<endl;
    }
    FM(const FM& f):food(f.food),cuisine(f.cuisine),rate(f.rate){
        //cout<<"拷贝构造函数被调用了"<<endl;
    }
    bool operator<(const FM &other) const
    {
        if (rate == other.rate)
            return food > other.food;
        return rate < other.rate;
    }
};

class FoodRatings {
    unordered_map<string, FM> fs;
    unordered_map<string, priority_queue<FM>> cs;
public:
    FoodRatings(vector<string> &foods, vector<string> &cuisines, vector<int> &ratings) {
        for (int i = 0; i < foods.size(); ++i) {
            string f = foods[i], c = cuisines[i];
            int r = ratings[i];
            FM fm(f,c,r);
            fs[f] = fm;
            cs[c].push(fm);
        }
    }

    void changeRating(string food, int newRating) {
        string c = fs[food].cuisine;
        fs[food].rate = newRating;
        cs[c].push(fs[food]);// 直接添加新数据,后面 highestRated 再删除旧的
    }

    string highestRated(string cuisine) {
        while(true){
            string f = cs[cuisine].top().food;
            if(fs[f].rate!=cs[cuisine].top().rate) cs[cuisine].pop();
            else break;
        }
        return cs[cuisine].top().food;
    }
};

还是使用了和set方案一样的自定义类FM,因为类里面已经重载了<操作符,所以直接一行代码就能实现初始化:

priority_queue<FM>

大顶堆对应的排序规则就是<或者仿函数less<>,如果是小顶堆则时>或仿函数greater<>,大顶堆就是队头元素最大。
除了重载操作符,也可以使用仿函数来实现,不过太麻烦,还是重载操作符来的直观。
不过,仿函数也有它的应用场合,比如下面我实现的哈夫曼树

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

struct TreeNode{
    int val;//权值
    int depth;//深度
    TreeNode* left,*right;
    TreeNode(int val):val(val),depth(0),left(nullptr),right(nullptr){}
    TreeNode(const TreeNode& that):val(that.val),depth(that.depth),left(that.left),right(that.right){}
};

class Cmp {
public:
    bool operator()(const TreeNode* a, const TreeNode* b) {
        return a->val>b->val;
    }
};

int getTotalWeight(TreeNode* root){
    int sum = 0;
    if(root->left==nullptr&&root->right==nullptr) return root->depth*root->val;//带权路径
    if(root->left!=nullptr){
        root->left->depth = root->depth+1;
        sum+=getTotalWeight(root->left);
    }
    if(root->right!=nullptr){
        root->right->depth = root->depth+1;
        sum+=getTotalWeight(root->right);
    }
    return sum;
}

int main()
{
    vector<int> weight = {5,29,7,8,14,23,3,11};
    priority_queue<TreeNode*,vector<TreeNode*>,Cmp> q;
    for(auto&w:weight) q.push(new TreeNode(w));
    while(q.size()>1){
        TreeNode* T1 = q.top();q.pop();
        TreeNode* T2 = q.top();q.pop();
        TreeNode* Root = new TreeNode(T1->val+T2->val);
        Root->left = T1;
        Root->right = T2;
        q.push(Root);
    }
    TreeNode* root = q.top();q.pop();
    int sum = getTotalWeight(root);
    cout<<"这棵树的带权路径和为:"<<sum<<endl;
    return 0;
}

关于set自定义排序方面,有一个绝佳应用,要不是碰到题目真心想不到,按对角线遍历,力扣题目1424

一开始我的想法是把矩阵补全,然后正常遍历,不过给的测试案例太变态,超时了,后来看到解题思路里面,有人提到了对角线上的元素i+j是相等的,基于此想到了构建一个struct对象

struct Point{
    int dragon;
    int j;
    int value;
    Point(int dragon,int j,int value):dragon(dragon),j(j),value(value){}
    bool operator<(const Point &other) const
    {
        if (dragon == other.dragon)
            return j < other.j;
        return dragon < other.dragon;
    }
};

class Solution {
public:
    vector<int> findDiagonalOrder(vector<vector<int>>& nums) {
        //缺列不缺行,补齐就行
        set<Point> st;
        for(int i=0;i<nums.size();i++){
            for(int j=0;j<nums[i].size();j++){
                st.insert(Point(i+j,j,nums[i][j]));
            }
        }
        vector<int> res;
        for(auto&s:st) res.push_back(s.value);
        return res;
    }
};

为了描述完整性,把之前矩阵补齐的代码也贴出来,说实话要不是测试案例太变态,不至于超时,也算一个不错的思路:

class Solution {
public:
    vector<int> findDiagonalOrder(vector<vector<int>>& nums) {
        //缺列不缺行,补齐就行
        int M = nums.size();
        int N = 0;
        for(auto&r:nums) N = max(N,(int)r.size());
        for(auto&r:nums) r.resize(N,0);
        vector<int> res;
        int i=0,j=0,c=0,row = 0,col = 1;
        bool rowFirst = true;
        while(c++<M*N){
            if(nums[i][j]!=0) res.push_back(nums[i][j]);
            if((i>0)&&(j<N-1)) {i-=1;j+=1;continue;}
            if(rowFirst){
                if((i==0)||(j==N-1))
                {
                    i=++row;j=0;
                    if(i==M){
                        i = M-1;j=1;rowFirst = false;
                    }
                }//顶到边界了
            }
            else{
                if((j==N-1)||(i==0))
                {
                    j=++col;i=M-1;
                    if(j==N){
                        break;
                    }
                }//顶到边界了
            }
        }
        return res;
    }
};

再补充一个优先队列使用lambda表达式自定义排序的例子

题目来自于力扣1696、跳跃游戏

class Solution {
public:
    int maxResult(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> dp(n,INT_MIN);
        dp[0] = nums[0];
        auto cmp = [&](int i, int j) -> bool 
        {
            return dp[i] < dp[j]; 
        };
        priority_queue<int,vector<int>,decltype(cmp)> q(cmp);
        q.push(0);
        for(int i=1;i<n;i++){
            while(q.top()<i-k) q.pop();
            dp[i] = dp[q.top()]+nums[i];
            q.push(i);
        }
        return dp[n-1];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TsubasaAngel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值