根据数据结构不同的排序算法
在STL中根据容器类型的不同,排序规则也有所区别。
- 序列式容器
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函数,需要注意的无外乎三个点
-
传入参数类型,必须是
随机访问迭代器
,必须包含排序区间[start, end)
,对于自定义数据类型,必须给定排序标准
-
排序标准的给定方式。我通常采用两种方式
- 直接匿名函数传入方式
- 定义一个
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;
}
};