1简述
C++中经常会遇到与排序相关的问题,最常见的就是sort函数和priority_queue容器。
对于基本数据类型,我们一般会按照升序或者降序来进行排列;而对于自定义的数据类型,我们通常需要自定义排序方案。
2 sort的使用
Sort函数使用模板: Sort(start,end,排序方法)
2.1 用于基本数据类型
以int类型为例。
1 ) 默认从小到大排列
sort默认下,也就是不适用第三个参数的时候,是升序排列。
//从小到大排列
vector<int> nums={13,4,6,1,23,5};
sort(nums.begin(),nums.end());
//这里注意,sort的前两个参数是迭代器类型;
//当传入指针作为迭代器的时候,第二个参数也要是尾后迭代器,所以这里是a+10
int a[10]={9,6,3,8,5,2,7,4,1,0};
sort(a,a+10,greater<int>());
2) 使用第三个参数实现从大到小排列
Sort函数的第三个参数可以用这样的语句告诉程序你所采用的排序原则
-
less<数据类型>():从小到大排序(这是默认使用的,所以一般不用)
-
greater<数据类型>():从大到小排序
注意:greater<>() 和 less<>() 都是头文件< functional >中的仿函数。库中写好了这俩仿函数,直接使用即可。(仿函数本质上是类对象,见STL源码解析)
//从大到小排列
vector<int> nums={13,4,6,1,23,5};
sort(nums.begin(),nums.end(),greater<int>());
//这里注意,sort的前两个参数是迭代器类型;
//当传入指针作为迭代器的时候,第二个参数也要是尾后迭代器,所以这里是a+10
int a[10]={9,6,3,8,5,2,7,4,1,0};
sort(a,a+10,greater<int>());
所以以后对基本类型进行从大到小排序,直接使用greater<>()第三个参数即可。
2.2 用于自定义数据类型
假设有一个自定义类:
class student
{
public:
int name;
int socre;
};
现有一个vector< student > vec,现在需要根据name实现从大到小和从小到大排序。
1)使用函数指针
bool less(const student &s1,const student s2)
{
return s1.name<s2.name;//从小到大排序
}
bool greater(const student &s1,const student s2)
{
return s1.name>s2.name;//从大到小排序
}
sort(vec.begin(), vec.end(), less);
sort(vec.begin(), vec.end(), greater);
注意:
- 这里的sort的第三个参数,是函数指针!
- 当sort是在某一个类中使用的时候,比较函数(less和greater)必须要写在类的外部(全局区域)或声明为静态函数!!!
- 当sort是在main中使用的时候,比较函数也要写在main外边(全局区域)。
2)使用仿函数
class less
{
public:
bool operator()(const Student& s1, const Student& s2)
{
return s1.name < s2.name; //从小到大排序
}
};
sort(vec.begin(), vec.end(), less());
这里的sort的第三个参数是仿函数,本质上是一个less类对象。
3)自定义比较运算符
这个方式的话,与上面两个不一样,这需要修改Student类的定义,在类中重载比较运算符。当然也可以声明为友元,通常重载比较运算符函数就是声明为友元函数的!
class student
{
public:
int name;
int socre;
bool operator <(const Student & s2) const
{
return name < s2.name;//按name从小到大排序
}
};
sort(vec.begin(),vec.end());
这个方法就是需要在类的内部做一定的修改。但是这个方法一般是最优先选择的。
4) 一个例子
#include <vector>
#include <algorithm>//包含sort和find算法
#include <iostream>
using namespace std;
class Test
{
public:
int a;
int b;
Test(int m_a, int m_b) {
a = m_a;
b = m_b;
}
//重载<运算符,先按a升序排列,若a相等,则按b升序排列;如果是降序就是重载">"运算符
bool operator < (const Test& testobj) const
{
if (a < testobj.a)
return true;
else if (a == testobj.a)
{
if (b < testobj.b)
return true;
else
return false;
}
else
return false;
}
//重载 == 运算符
bool operator == (const Test& testobj) const
{
//如果a相等,则判断为相等
//return a == testobj.a;
//如果a和b都相等,则判断为相等
return (a == testobj.a) && (b == testobj.b);
}
};
//类外定义一个比较函数compare
bool compare(const Test& t1, const Test& t2) {
if (t1.a != t2.a)
return t1.a < t2.a;
else if(t1.b != t2.b)
return t1.b < t2.b;
return false;
}
int main()
{
vector<Test> vec;
vec.push_back(Test(1, 1));
vec.push_back(Test(3, 6));
vec.push_back(Test(2, 1));
vec.push_back(Test(3, 4));
//排序sort
sort(vec.begin(), vec.end());//类中定义好排序规则
//sort(vec.begin(), vec.end(), compare);//类外定义一个比较函数
for (int i = 0; i< (int)vec.size(); ++i)
{
cout << vec[i].a <<" "<< vec[i].b <<endl;
}
//查找元素
vector<Test>::iterator ite = find(vec.begin(), vec.end(), Test(3, 4));
if(ite!=vec.end())
{
cout << "已找到" << ite->a << " " << ite->b << endl;
}
else {
cout << "cannot find " << endl;
}
system("pause");
return 0;
}
3 priority_queue的使用
虽然堆本质上是一个完全二叉树,但是他的底部是一个vector。
STL堆的底层是一个vector
定义:priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆。
3.1 优先级队列存放的是基本数据类型
以int为例
1)定义大根堆
priority_queue<int> a;
//等同于priority_queue<int, vector<int>, less<int> > a;
2)定义小根堆
priority_queue<int, vector<int>, greater<int> > c; //这样就是小顶堆
这里需要注意的是:
- 如同上面的sort一样,默认是使用less,而不一样的是,sort使用less是进行从小到大的排序。而priority_queue则是使用less创建大根堆!!!
- 另外最重要的一点是:sort是函数,所以,sort的第三个参数是类对象,因此可以传入仿函数(本质上是类对象);而priority_queue模板类,是一种类型,它的三个参数都是类型!!因此传入的应该是类型!
3.2 优先级队列存放的是自定义数据类型
假设存放的是Student类对象。
class Student
{
public:
int name;
int socre;
Student(int a,int b)
{
name=a;
score=b;
}
};
现在把n个student对象放到优先级队列中。大小比较策略是根据name的大小。
1)运算符重载
这个方式,要对类的定义进行修改,或者使用友元函数进行运算符的重载。
class Student
{
public:
int name;
int socre;
Student(int a,int b)//构造函数
{
name=a;
score=b;
}
bool operator <(const Student & s2) const
{
return name < s2.name;//在sort中是按name从小到大排序,在priority_queue则会构建大根堆
}
};
Student stu1(1,1);
Student stu1(3,1);
Student stu1(2,1);
priority_queue< Student > d;
d.push(stu1);
d.push(stu2);
d.push(stu3);
运算符重载是一个一劳永逸的方式。只要对类的运算符进行了重载,在涉及到对类对象进行大小比较的时候,就不需要去额外设计一些东西了。比如,类中如果进行了运算符的重载,那么对这个类对象使用sort等设计到大小比较排序函数、使用priority_queue等里面涉及到大小比较的数据结构的时候,都不用额外的考虑某些东西了。
2) 仿函数
class comp//重写仿函数
{
public:
bool operator()(const Student& s1, const Student& s2)
{
return s1.name < s2.name; 在sort中是按name从小到大排序,在priority_queue则会构建大根堆
}
};
Student stu1(1,1);
Student stu1(3,1);
Student stu1(2,1);
// 这是错误的,comp不是类模板
//priority_queue<Student,vector<Student>,comp<Student>> d;
priority_queue<Student,vector<Student>,comp> d;
d.push(stu1);
d.push(stu2);
d.push(stu3);
实际上这个方法的本质和3.1节是一样的,只不过3.1用的是系统库里面自带的仿函数,而这里,我们根据自定义类型的具体情况,进行仿函数的重写。如同3.1节,这里我们重写了仿函数后,将Student类对象放入优先级队列中就如同将int类型的变量放入优先级队列一样。
如同3.1节,这里的第三个参数同样不是对象,而是类型!!
3) 第三个参数不能使用函数指针
函数指针是一个函数对象,因此不能像泛型算法那样,传入函数指针!
第三个参数只能是类型!!
4)一个例子
#include <iostream>
#include <queue>
using namespace std;
struct Student //运算符重载<
{
int name;
int score;
Student(int a,int b) {
name = a;
score=b;
}
//方法1,运算符重载
bool operator<(const Student& a) const
{
return name < a.name; //大顶堆
}
};
//方法2
struct comp //重写仿函数
{
bool operator() ( const Student a, const Student b)
{
return a.name < b.name; //大顶堆
}
};
int main()
{
Student stu1(1,1);
Student stu2(3,1);
Student stu3(2,1);
priority_queue<Student> d;
d.push(stu1);
d.push(stu2);
d.push(stu3);
while (!d.empty())
{
cout << d.top().name << endl;
d.pop();
}
cout << endl;
priority_queue<Student,vector<Student>,comp> e;
e.push(stu1);
e.push(stu2);
e.push(stu3);
while (!e.empty())
{
cout << e.top().name << endl;
e.pop();
}
return 0;
}