文章目录
一、结论
1.排序和建堆的效果
- 排序:
less<T>
变成升序(从左到右遍历下标时,数组元素是从小到大)
greater<T>
变成降序(从左到右遍历下标时,数组元素是从大到小) - 建堆:
less<T>
变成大顶堆(从上层到下层,堆元素是从大到小,同层之间随便)
greater<T>
变成小顶堆(从上层到下层,堆元素是从小到大,同层之间随便)
可以看到排序和建队时,less
和greater
并不是直接对应汉语意思,不能统一。其实是真正的意思是两个要比较的元素,第一个元素是否比第二个元素更小less还是更大greater。
2.解释结论
-
排序
上面的例子应该就看懂了吧。less
就是让前一个比后一个更小;greater
就是让前一个比后一个更大。谁会是a
,谁会是b
,是按照排序算法的。 -
建堆
顶堆插入一个新元素时,就是插入到最后一个叶子。
然后这时候整理堆内元素让堆重新满足大小顶堆。关键让新插入的结点和它的父结点进行比较,comp(新插入,它的父结点)
。
大顶堆就是让父比子大,即符合less
让新插入的比父结点更小;
小顶堆就是父比子小,即符合greater
让新插入的比父结点更大。
二、解析
1.比较规则:strict weak ordering
std::greater()
、std::less()
、自定义比较函数,这些都其实是用作比较的,要遵从c++制定的比较规则。
需要满足三种特性要求,否则使用中会报错:
- 反自反性:
false
true
的互斥性:true
则false
(但不要求false
则怎么样)- 传递性:
true
+true
则true
2.less和greater其实是什么
比如less
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()
和return x<y;
bool operator()
:要的就是这个返回值bool
,决定比较是否要交换。这个结果用在排序和建堆中就表示是否要交换。return x<y;
:可以看到其实就是使用<
之类的操作符重载,这就是怎么排序的规则。
PS:但这产生了限制,基本的元素int
之类的,自然可以直接比较;但复杂类型如自定义一个类,里面有多个数据,我们就还得定义重载操作符比较,要不然编译器不知道该比较什么。
3.bool返回值和比较操作符
(1)规则
bool comp(a, b)
意思是:返回的值指示作为第一个参数传递的元素是否被视为在其定义的特定严格弱排序中位于第二个参数之前。
- 返回
true
:表示ab
(a
在b
前) - 返回
false
:表示ba
(a在
b`后)
op | 1 op 2 | 2 op 1 | 2 op 2 | 结果 |
---|---|---|---|---|
< 即 less | true,ab ,不交换 | false,ba ,交换 | false,ba ,交换 | 数组升序、大顶堆 |
> 即 greater | false,ba ,交换 | true,ab ,不交换 | false,ba ,交换 | 数组降序、小顶堆 |
<= | true,ab ,不交换 | false,ba ,交换 | true,ab ,不交换 | 代价更小的数组升序、大顶堆 |
>= | false,ba ,交换 | true,ab ,不交换 | true,ab ,不交换 | 代价更小的数组降序、小顶堆 |
== | false,ba ,交换 | false,ba ,交换 | true,ab ,不交换 | 意义不明 |
!= | true,ab ,不交换 | true,ab ,不交换 | false,ba ,交换 | 意义不明 |
(2)并不是想当然的位置交换
comp(a, b)
虽然会交换a
和b
,但你不能想当然地认为位置就该怎么样,到底数组中谁会是a
,谁会是b
,这要看调用的算法的。
比如,[a < b][6 < 1] : 0
,其实是算法调用时a
是6,b
是1,而非看到数组中原来的顺序就想当然的a
是1,b
是6。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// 重写排序方法
// 常引用const T &xxx
bool comp(const int &a, const int &b)
{
printf("[a < b][%d < %d] : %d\n", a, b , a<b);
return a < b;
}
int main()
{
vector<int> v = {2, 3, 1, 6, 2, 5, 4};
sort(v.begin(), v.end(), comp);
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
return 0;
}
/*
[a < b][3 < 2] : 0
[a < b][3 < 2] : 0
[a < b][1 < 2] : 1
[a < b][6 < 1] : 0
[a < b][6 < 3] : 0
[a < b][2 < 1] : 0
[a < b][2 < 6] : 1
[a < b][2 < 3] : 1
[a < b][2 < 2] : 0
[a < b][5 < 1] : 0
[a < b][5 < 6] : 1
[a < b][5 < 3] : 0
[a < b][4 < 1] : 0
[a < b][4 < 6] : 1
[a < b][4 < 5] : 1
[a < b][4 < 3] : 0
1 2 2 3 4 5 6
*/
(3)<和<=代价证明
大致测试了一下,好像0
的次数(则要交换的次数)更少,应该<=
比<
更好吧。
同理>=
和>
PS:还没有证明和具体的排序算法是否有关系,应该没吧。o( ̄▽ ̄)o
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// 重写排序方法
// 常引用const T &xxx
bool comp(const int &a, const int &b)
{
printf("[a <= b][%d <= %d] : %d\n", a, b, a <= b);
return a <= b;
}
int main()
{
vector<int> v = {2, 3, 1, 6, 2, 5, 4};
sort(v.begin(), v.end(), comp);
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
return 0;
}
/*
[a <= b][3 <= 2] : 0
[a <= b][3 <= 2] : 0
[a <= b][1 <= 2] : 1
[a <= b][6 <= 1] : 0
[a <= b][6 <= 3] : 0
[a <= b][2 <= 1] : 0
[a <= b][2 <= 6] : 1
[a <= b][2 <= 3] : 1
[a <= b][2 <= 2] : 1
[a <= b][2 <= 1] : 0
[a <= b][5 <= 1] : 0
[a <= b][5 <= 6] : 1
[a <= b][5 <= 3] : 0
[a <= b][4 <= 1] : 0
[a <= b][4 <= 6] : 1
[a <= b][4 <= 5] : 1
[a <= b][4 <= 3] : 0
1 2 2 3 4 5 6
*/
三、自定义
符合两个条件:
bool
:返回值bool
return x<y;
:重载<
之类的操作符,并且要决定比较什么元素。- PS:建议还要常引用,保险,禁止发生修改要比较的元素可能。
1.数组
- 函数:使用时不加括号,加了报错
- 类的对象:注意,排序时的类必须使用类的对象才对,直接使用类报错。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// 重写排序函数
bool cmpfunc(const int &a, const int &b)
{
return a < b;
// < 升序; > 降序
}
// 模仿less、greater构建类
struct cmpClass
{
bool operator()(const int &i, const int &j)
{
return (i < j);
}
}cmpClassObject; // 注意,排序时的类必须使用类的对象才对,使用类报错。
int main()
{
// 使用函数
vector<int> v1 = {2, 3, 1, 6, 2, 5, 4};
// 使用时不加括号,加了报错
sort(v1.begin(), v1.end(), cmpfunc);
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
// 1 2 2 3 4 5 6
// 使用类的对象
vector<int> v2 = {2, 3, 1, 6, 2, 5, 4};
sort(v2.begin(), v2.end(), cmpClassObject);
for (int i = 0; i < v2.size(); i++)
{
cout << v2[i] << " ";
}
cout << endl;
// 1 2 2 3 4 5 6
return 0;
}
2.优先级队列
- 定义类时同时定义操作符重载函数:操作符重载函数,必须是具体的操作符
<
之类的,写()报错 - 自定义类,自定义比较函数:操作符重载函数,必须是具体的操作符
<
之类的,写()报错 - 自定义类,自定义包含比较函数的结构体:操作符重载函数,必须是写
()
#include <iostream>
#include <queue>
using namespace std;
/******** 定义类时同时定义操作符重载函数 ********/
struct Node1
{
// 要比较的元素
int x;
// 构造函数
Node1(int x) { this->x = x; }
// 操作符重载函数,必须是具体的操作符<之类的,写()报错
bool operator<(const Node1 &b) const
{
// 实现less中需要的<,大顶堆
return x < b.x;
}
};
/******** 自定义类,自定义比较函数 ********/
struct Node2
{
// 要比较的元素
int x;
// 构造函数
Node2(int x) { this->x = x; }
};
// 操作符重载函数,必须是具体的操作符<之类的,写()报错
bool operator<(const Node2 &a, const Node2 &b)
{
// less,大顶堆
return a.x < b.x;
}
/******** 自定义类,自定义包含比较函数的结构体 ********/
struct Node3
{
// 要比较的元素
int x;
// 构造函数
Node3(int x) { this->x = x; }
};
struct cmpClass
{
// 操作符重载函数,必须是写()
bool operator()(const Node3 &a, const Node3 &b)
{
// less,大顶堆
return a.x < b.x;
}
};
int main()
{
/******** 初始化优先级队列的对象p ********/
// Node1类型,默认使用vector,小顶堆,同 priority_queue<Node1, vector<Node1>, less<Node1> > p;
priority_queue<Node1> p;
// 乱序入队
p.emplace(1);
p.emplace(3);
p.emplace(2);
// 弹出队首
while (!p.empty())
{
cout << p.top().x << " ";
p.pop();
}
cout << endl;
// 3 2 1
/******** 初始化优先级队列的对象q ********/
// 同 priority_queue<Node2> q;
priority_queue<Node2, vector<Node2>, less<Node2>> q;
// 乱序入队
q.emplace(1);
q.emplace(3);
q.emplace(2);
// 弹出队首
while (!q.empty())
{
cout << q.top().x << " ";
q.pop();
}
cout << endl;
// 3 2 1
/******** 初始化优先级队列的对象r ********/
priority_queue<Node3, vector<Node3>, cmpClass> r;
// 乱序入队
r.emplace(1);
r.emplace(3);
r.emplace(2);
// 弹出队首
while (!r.empty())
{
cout << r.top().x << " ";
r.pop();
}
cout << endl;
// 3 2 1
return 0;
}
Reference
一个std::sort 自定义比较排序函数 crash的分析过程
关于STL中的greater()和less()
C++官网:less
C++ std::优先级队列priority_queue