C++中sort排序以及map自定义排序规则初探

3 篇文章 1 订阅

根据数据结构不同的排序算法

在STL中根据容器类型的不同,排序规则也有所区别。

  1. 序列式容器vector,deque,array等可以通过sort进行排序。因为查看std::sort的接口可以发现,他要求传入的容器迭代器类型为RandomAccessIterator。
    在这里插入图片描述
    而常用的容器的迭代器类型继承关系如下
    在这里插入图片描述
    因此,对于关联式容器或者链表list,当我们需要插入自定义数据类型时,我们需要给定内部的排序规则。
    接下来就逐一展开描述

STL中algorithm提供的sort函数

已经有许多现成的学习资料,我就不重复过多。
【C++&Leetcode】浅析map与sort的自定义排序

需要注意以下几个问题:

  • sort函数传入的自定义比较规则可以是两种类型

    • 函数指针
    • 函数对象

因此,我们通常可以采用三种方式设定自己的规则

//1. 构造仿函数
struct comp{
	bool operator()(const T& lhs, const T& rhs)
	{
		//逻辑实现
	}
} myComp; //这时候可以自动创建一个myComp对象

//2. 构造一个普通的bool函数
bool compFunc(const T& lhs, const T& rhs)
{
	//逻辑实现
}

//3. 直接在传入参数的时候,构建一个lambda函数
//(这个操作显得比较高端,适合只需要定义一次排序规则的时候)

这时候,在我们的排序函数sort中就可以有如下的几种调用形式

vector<T> vec;
//vec的一些赋值操作
sort(vec.begin(), vec.end(), myComp); //传入创建好的函数对象
sort(vec.begin(), vec.end(), comp()); //通过class()生成临时函数对象
sort(vec.begin(), vec.end(), compFunc); //传入函数指针

至于比较秀操作一点的匿名函数写法,也有两种方式

假定我们vector中要存放的数据是一个pair<string, int>分别表示学生姓名和成绩,并且希望按照成绩由高到低排列,如果分数相同,则按照姓名从先到后排列。那么就可以通过匿名函数快速定义排序规则

vector<pair<string,int>> data;
//sort内部定义匿名函数
sort(data.begin(), data.end(), [](pair<string, int>& d1, pair<string, int>& d2)
{return d1.second == d2.second? d1.first < d2.first : d1.second > d2.second;});
 //注意,这里的pair<string, int>不能改为auto

//外部定义匿名函数
 auto cmp = [](pair<string, int>& d1, pair<string, int>& d2){return d1.second == d2.second? d1.first < d2.first : d1.second > d2.second;};

sort(data.begin(), data.end(), cmp);

小结

对于sort函数,需要注意的无外乎三个点

  1. 传入参数类型,必须是随机访问迭代器,必须包含排序区间[start, end),对于自定义数据类型,必须给定排序标准

  2. 排序标准的给定方式。我通常采用两种方式

    • 直接匿名函数传入方式
    • 定义一个struct并重载operator()或者定义一个普通函数(需要注意函数对象、函数指针的区别)

map、set的排序规则

还是回到迭代器的继承关系图
在这里插入图片描述
因为,map和set内部实现是红黑树,因此在插入数据的过程中便自动做好了排序。因此其不再支持sort后期排序。因此我们需要在定义map或者set对象的时候就指定其排序规则。

话题背景还是刚刚的pair<string, int>的排序问题。我们知道,map的元素类型就是pair。并且当key的类型为C++基本数据类型T的时候,默认按照less<T>进行排序;

template <class T> struct less {
  bool operator() (const T& x, const T& y) const {return x<y;}
  typedef T first_argument_type;
  typedef T second_argument_type;
  typedef bool result_type;
};
//这是一个二元谓词,也就是需要传入两个参数,并返回bool类型。前提是这两个数据类型能够进行operator<比较操作。
//因此这也要求我们:如果想要自定义数据类型支持less排序规则,需要重载运算符<

但通常情况下,我们都自定义数据类型了,肯定有不一样的需求。因此我们可以将我们的排序规则定义成一个仿函数(参考上一节)并且在声明map或者set对象的时候作为参数传入

//参考网址:http://www.cplusplus.com/reference/map/map/map/
// constructing maps
#include <iostream>
#include <map>

bool fncomp (char lhs, char rhs) {return lhs<rhs;}

struct classcomp {
  bool operator() (const char& lhs, const char& rhs) const
  {return lhs<rhs;}
};

int main ()
{
  std::map<char,int> first;

  first['a']=10;
  first['b']=30;
  first['c']=50;
  first['d']=70;

  std::map<char,int> second (first.begin(),first.end());

  std::map<char,int> third (second);

  std::map<char,int,classcomp> fourth;                 // class as Compare

  bool(*fn_pt)(char,char) = fncomp;
  std::map<char,int,bool(*)(char,char)> fifth (fn_pt); // function pointer as Compare

  return 0;
}

//对于set:http://www.cplusplus.com/reference/set/set/set/
// constructing sets
#include <iostream>
#include <set>

bool fncomp (int lhs, int rhs) {return lhs<rhs;}

struct classcomp {
  bool operator() (const int& lhs, const int& rhs) const
  {return lhs<rhs;}
};

int main ()
{
  std::set<int> first;                           // empty set of ints

  int myints[]= {10,20,30,40,50};
  std::set<int> second (myints,myints+5);        // range

  std::set<int> third (second);                  // a copy of second

  std::set<int> fourth (second.begin(), second.end());  // iterator ctor.

  std::set<int,classcomp> fifth;                 // class as Compare

  bool(*fn_pt)(int,int) = fncomp;
  std::set<int,bool(*)(int,int)> sixth (fn_pt);  // function pointer as Compare

  return 0;
}

利用上面关于map和set的几种初始化方式,已经能够实现对于自定义数据类型,作为key的时候,制定我们需要的排序规则

例如:给定若干个family,每个family有两个属性:家庭成员数量,人均收入。我们希望对其进行一个排序,按照收入从高到低,相等时按照成员数量由大到小进行排序。那么其实现方式如下

//测试set的自定义排序规则
#include <set>
#include <iostream>
#include <vector>

using namespace std;

struct family{
    int members;
    float salary;
    family(int m, float s): members(m), salary(s){}
};

struct com{
    bool operator()(const family& lhs, const family& rhs)
    {
        return lhs.salary == rhs.salary? lhs.members > rhs.members : lhs.salary > rhs.salary;
    }
};
void testSet()
{
    set<family, com> st; 
    vector<family> vec{family(3, 10), family(4, 10), family(4, 11)};
    for(auto& f: vec)
    {
        st.insert(f);
    }
    for(auto& f: st)
    {
        cout << "family salary: " << f.salary << " family members: " << f.members << endl;
    }

    return;
}

int main()
{
    testSet();

    return 0;
}

在这里插入图片描述
但是!!!当我们在统计一些数据的时候,如词频,我们需要通过map来去重,并且通过value来记录统计次数。并且输出前K个最大频次的单词。最开始我想的是,将谓词的排序规则进行相应的设定。但是结果事与愿违。

现在分析来看,原因是因为map在插入的时候,要求作为排序规则的元素不能够更改。必须具有唯一性。因此,我们的value在变是没办法作为map的排序准则的。

解决办法:
在插入统计完成之后,通过vector将map数据进行装载,然后sort自定义规则排序
题目来源:LeetCode的前K个高频单词

struct comp{
    bool operator()(pair<string, int>& p1, pair<string, int>& p2){
            if(p1.second == p2.second) return p1.first < p2.first;
            else return p1.second > p2.second;}
} compObj;
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string, int> mp;
        for(auto& word: words)
        {
            mp[word]++;
        }
        vector<pair<string, int>> a(mp.begin(), mp.end());
        sort(a.begin(), a.end(), compObj);
        vector<string> res;
        auto cur = a.begin();
        for(int i = 0; i < k; i++)
        {
            res.emplace_back(cur->first);
            advance(cur, 1);
        }
        return res;
    }
};
  • 10
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值