C++中利用sort进行排序

C++中利用sort进行排序

编写C++经常需要使用sort进行排序,有可能是简单的数组、数字vector或者是复杂一点的存放对象的vector。

C++为了满足用户的需求,在algorithm里面封装了sort泛型算法。所以使用时,必须#include < algorithm>

template <class RandomAccessIterator>
  void sort (RandomAccessIterator first, RandomAccessIterator last);
 
template <class RandomAccessIterator, class Compare>
  void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

可以看见,sort原型分为两个,区别在于第一个函数有两个参数,第一个函数有三个参数。

其中两个函数都有的是 RandomAccessIterator 是随机访问迭代器,first是初始位置,last是末尾位置,默认使用迭代器引用的 operator < 进行排序。

第二个函数,前两个参数一样,也是用来说明从哪儿到哪儿排序。第三个参数是Compare,意思是使用comp这个“方法”对对象进行排序。comp可以是函数对象或者是函数指针,具体使用请看后面。

两个参数排序

使用两个参数这应该是最普遍也是最简单的情景,如果只有两个参数,默认使用 operator < 对数组排序,结果为升序。

对数组排序:

 int arr[] = { 9,8,7,6,5,4,3,2,1 };
 sort(arr, arr + 9);

需要注意的是,这里传入的是迭代器,所以要传入头指针和末尾指针(最后一个待排元素的后一个位置)

对vector排序:

vector<int> arr;
for (int i = 9; i > 0; i--)
    arr.push_back(i);
sort(arr.begin(), arr.end());

这里直接传入vector的begin和end两个迭代器就对整个vector完成了排序。

对象排序

如果只使用两个参数的话,要对对象排序,那么只能依靠重载运算符来实现。而且必须重载的是 < 关系运算符。

class Test {
public:
    int value;
    Test() : value(0) {};
    Test(int x) : value(x) {};

    // 重载运算符
    bool operator < (const Test& t) {
        if (value < t.value)
            return true;
        return false;
    }
};

...

vector<Test> arr;
for (int i = 9; i > 0; i--) {
    arr.push_back(Test(i));
}
sort(arr.begin(), arr.end());

这样,就根据Test类中value的值来升序排对象的顺序了。

使用三个参数排序

那么有读者肯定会问,如果我想要降序排列,或者我有我自己的比大小规则,或者对于对象我不想重载运算符怎么办呢?

那么这里就要用到我们sort的第三个参数了,之前提到了,第三个参数是决定以什么“方法”进行排序的。可以传入函数对象和函数指针。那么这个参数如何填入,如何编写便成了最主要的问题。

使用自定义函数

假如我们还是存的之前那个类,唯一的区别是,我不再重载运算符了,那要怎么办呢?

class Test {
public:
    int value;
    Test() : value(0) {};
    Test(int x) : value(x) {};
};

那么,我们可以自己自定义编写比较函数,来决定两个元素是如何比较的。

bool cmp_func(const Test& t1, const Test& t2) {
    if (t1.value < t2.value)
        return true;
    return false;
}

vector<Test> arr;
for (int i = 9; i > 0; i--) {
    arr.push_back(Test(i));
}
sort(arr.begin(), arr.end(), cmp_func);

如上,定义了一个cmp_func,直接将sort的第三个参数指定为cmp_func就可以对迭代器引用区的元素进行排序。例如这里可以实现根据Test中value的值来实现升序排列这些对象了。

当然,也可以通过修改比较函数来实现降序。

bool cmp_func(const Test& t1, const Test& t2) {
    if (t1.value > t2.value)
        return true;
    return false;
}

使用函数对象

函数对象其实很简单,可以很好的代替函数指针,但它理解起来非常简单,重载了()运算符的对象就叫函数对象。之所以叫函数对象,是因为它的调用可以和函数看起来一样。

class Test {
public:
    int value;
    Test() : value(0) {};
    Test(int x) : value(x) {};

    // 重载运算符
    bool operator () (const Test& t1, const Test& t2) {
        if (t1.value < t2.value)
            return true;
        return false;
    }
};

vector<Test> arr;
for (int i = 9; i > 0; i--) {
    arr.push_back(Test(i));
}
sort(arr.begin(), arr.end(), Test());

这个原理其实就是第三个参数,临时创建了一个Test对象,假设这个对象为t。

那么代码就是 sort(arr.begin(), arr.end(), t)

然后会把比较的两个元素,通过函数对象的调用 t(t1, t2)来进行比较,所以他会正常工作。

sort排序利用的是 快排/堆排/插排,所以并不稳定,如果对稳定性有需求,请使用stable_sort()

究竟怎么比?

那么,其实读到这里,细心的读者会想问,我引入自定义函数和函数对象后,什么叫“大”?什么叫“小”?我要怎么得到理想的排列顺序?

例如,为什么降序就是那么自定义函数,返回值是true又发生了什么?。

实际调用sort时,第三个参数Compare comp对应的是一个函数对象或者函数的名字。comp对应的功能,是教sort怎么排序的,我们可以称之为“比较器”。

其实,前面提到了,sort算法比较元素,默认是通过 < 运算符进行的,达到的效果是它天然就是要升序排列。那我们可以理解为,它其实默认写了一个comp是这样的。

bool comp(const T& t1, const T& t2) {
    if (t1.value < t2.value)
        return true;
    return false;
}

如果把传入的第一个元素比第二个元素成立(返回值为true),那么会把第一个元素往前挪,第二个元素往后挪。

那么我们现在把改为任意比较 @。如果第一个元素@第二个元素成立,那么会把第一个元素往前挪,第二个元素往后挪。

所以把@改成>,就实现了降序排列,因为如果第一个元素比第二个大,那么大的都往前挪,小的都往后挪,自然就是降序了。

学术一点来说,STL关联容器中的元素是从小到大排序的。使用关联容器时,可以用自定义的“比较器”来取代 < 运算符,以规定元素之间的大小关系。所以以下三者是等价的:

  • x < y
  • bool comp(x, y)的返回值为true
  • y > x

也就是说,我如果从逻辑上遵循了这个规则,那么会达到升序的效果。

那么把小于改成任意关系运算符@,在自定义比较关系时 bool comp(x, y),以下三条是等价的

  • x@y
  • bool comp(x, y)的返回值为true
  • y !@ x

(!@表示@的逆关系)

例如把这个@替换为 > ,如何规定自己的比较函数和函数对象就显而易见了。

posted @ 2020-06-06 01:05  scyq  阅读(1336)  评论(0编辑  收藏  举报