diff --git a/.DS_Store b/.DS_Store index 177c640..f1b10fd 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/content/.DS_Store b/content/.DS_Store index 4d58e1d..8093750 100644 Binary files a/content/.DS_Store and b/content/.DS_Store differ diff --git a/content/post/.DS_Store b/content/post/.DS_Store index 3ac4fbb..b12c465 100644 Binary files a/content/post/.DS_Store and b/content/post/.DS_Store differ diff --git a/content/post/2019/07/05/std-sort-cpp.md b/content/post/2019/07/05/std-sort-cpp.md new file mode 100644 index 0000000..503434a --- /dev/null +++ b/content/post/2019/07/05/std-sort-cpp.md @@ -0,0 +1,220 @@ +--- +categories: +- "技术志" +tags: +- "c++" +- "STL" +date: 2019-07-05 +title: "C++ 中基于谓词的 std::sort 介绍" +url: "/2019/07/05/std-sort-cpp" +--- + +数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 `std::sort`, 本篇主要介绍基于谓词的 `std::sort` 以及常见使用场景。 + + + +### C++ 中的排序方法概述 +C++ 中 `std::sort` 是一个标准的排序算法, 它的语法声明如下: +```cpp +template +void sort ( RandomAccessIterator first,RandomAccessIterator last ); + +template +void sort ( RandomAccessIterator first,RandomAccessIterator last, StrictWeakOrdering comp ); +``` +参数中通过 `first`、 `last` 作为需要排序的区间,一般比较多的使用场景是针对 `std::vector` 或者数组进行排序 (其他 STL 例如 `std::map`, `std::set` 本身存储的是已经排好序的)。当然,从声明中我们可以看到迭代器类型是随机访问的,也就是可以对支持随机访问迭代器的容器(`Container`)进行排序。 + +`std::list` 则不能通过 `std::sort` 方法排序, 因为它的迭代器不是随机访问迭代器。如果你想要对 `std::list` 进行排序,你需要使用 `std::list::sort` 成员方法进行排序。 + +`std::sort`所使用的排序算法取决于不同的编译器,但是时间复杂度一般不会超过 `O(N log N)`,其中 `N` 为元素个数。通常 `std::sort` 的实现会基于不同排序算法的优缺点组合实现。其中一个算法是 `IntroSort`。 +`IntroSort` 是一种混合的排序算法,其目标就是提供一种排序算法,它具有快速的平均时间复杂度和最优的最差时间复杂度。它结合了快速排序和堆排序,结合了两者的有点。 其平均和最差时间复杂度都是 `O(N log N)`。 + +理论上,对数字进行排序和对任何数据进行排序是没有任何区别的。它们使用相同的算法,唯一的差别是不同的数据类型使用了不同的比较方法。 + +#### String 类型排序 +任何带有 `RandomIterator` 的容器都可以使用 `std::sort` 进行排序,`std::string` 也实现了随机访问迭代器,下面的示例展示了对 `Hello World` 字符串的排序: +```cpp +#include +#include +#include + +std::string hello{ "Hello World!" }; +std::sort(hello.begin(), hello.end()); +std::cout << hello << "\n"; +``` + +输出结果为 ` !HWdellloor`, 可以看到输出结果是严格按照字母递增排序的。 + +### 对结构或者类的排序 +如果某个数据可以通过小于操作符 `<` 进行比较,那么当该数据位于支持随机访问的容器中时, 就可以对其进行排序。 +考虑到下面是的示例: +```cpp +struct some_data +{ + some_data() = default; + some_data(int val) : a(val) {} + int a; +}; + +std::vector elements = { 0,9,2,7,3,5,7,3 }; +std::sort(elements.begin(), elements.end()); +``` + +当运行这断代码时,你将会得到下面的编译错误,编译器没有找到合适的 `operator <` 比较操作去比较两个 `some_data` 实例。 +``` + error: no match for ‘operator<’ (operand types are ‘some_data’ and ‘some_data’) +``` + +通常有三种方式解决这问题: +* 在类的内部,实现 `operator<` 成员函数的重载 +* 在类的外部,实现 `operator<` 非成员函数的重载 +* 通过谓词(`predicate`)来实现 + +#### 成员函数 operator < 的重载 +如果这种比较函数仅仅针对这个类/结构体本身使用,成员函数 `operator < ` 的重载是最简单和简洁的一种方式。 +还是使用上面的 `some_data` 作为例子,在结构体中使用下面声明的方法 (`rhs` 表示 `right hand side`): +```cpp +bool operator < (const some_data& rhs) const +``` + +结构体定义如下: +```cpp +struct some_data +{ + some_data() = default; + some_data(int val) : a(val) {} + + int a; + + bool operator<(const some_data & rhs) const + { + return a < rhs.a; + } +}; +``` + +再次使用 `std::sort` 将会调用到自定义的重载的小于操作符。 + +#### 非成员函数 operator< 的重载 +如果你不能改变类或者结构体的话,可以使用非成员函数 `operator <` 的重载,在类的外部定义下面的方法: +```cpp +bool operator<(const some_data & lhs, const some_data & rhs) +{ + return lhs.a < rhs.a; +} +``` + +#### 基于谓词(predict)的排序 +对于排序,一个谓词表示对象 `A` 是否在对象 `B` 之前,因此一个搜索谓词必须有两个相同类型的参数。 +假设下面的一个汽车的结构,每辆汽车都有特定的属性,如制造商、型号、年份和价格。 +```cpp +struct car +{ + std::string make; + std::string model; + int year; + double price; +}; +``` +我们可以基于汽车的某个属性进行排序。 + +* Lambda 作为排序谓词 + +使用 `lambda` 表达式作为谓词也许是最简单和高效的一种实现方式。`lambda` 是一种匿名方法,编译器能够更加容易对其进行内联和优化,同样它相比普通函数而言也更加容易阅读。 +我们可以定义下面基于 `lambda` 的谓词对制造商、型号进行排序。 +```cpp +auto sortByMakeAndModel = + [](const car & a, const car & b) -> bool +{ + if (a.make < b.make) return true; + if (a.make > b.make) return false; + if (a.model < b.model) return true; + if (a.model > b.model) return false; + + return false; +}; + +std::sort(first, last, sortByMakeAndModel); +``` +或者下面的表达式: +```cpp +auto sortByMakeAndModel = + [](const car & a, const car & b) -> bool +{ + return + ( + (a.make < b.make) || + (a.make == b.make && a.model < b.model) + ); +}; + +std::sort(first, last, sortByMakeAndModel); +``` +这两种表达式都是等价的。 + +* 非成员函数作为谓词 + +非成员函数是没有定义在类或者结构体中的方法,它的行为像 `lambda`,但是可以出现在多个编译模块。出于性能原因,使用非成员方法可以声明为 `inline`。我们可以给编译器一个提示,使用 `inline` 来内联该方法。 +```cpp +inline bool sortYear(const car & a, const car & b) +{ + return a.year < b.year; +} + +std::sort(first, last, &sortYear); +``` + +* 成员函数作为谓词 + +使用成员方法需要基于类或结构的实例。`c++11` 引入了 `std::bind`。它简化了绑定到类中的方法,但是在构造 `std::bind` 对象时必须小心。 +```cpp +struct car_sort +{ + bool sortYear(const car & a, const car & b) const + { + return a.year < b.year; + } +}; +``` +这里必须要声明一个类的可用的实例,`car_sort sortInstance`, `sortYear` 这个方法也必须要声明为 public 。 +```cpp +std::sort( // (1) Call to std::sort + first, // (2) First iterator + last, // (3) End iterator + std::bind( // (4) Predicate, which is std::bind + &car_sort::sortYear, // (5) Pointer to member method + sort_instance, // (6) Instance of struct + std::placeholders::_1, // (7) Placeholder for argument 1 + std::placeholders::_2 // (8) Placeholder for argument 2 + ) +); +``` + +使用 `std::bind` 有可能导致运行时的开销,因为通过这种方式调用的谓词本质上是一个函数指针,与任何指针一样它可能在运行时被更改。编译器可能会优化哪些在运行时不会被更改的指针,这也是动态绑定的一个例子。 +使用 `lambda` 表达式,可以获得静态绑定的指针,也就是 `lambda` 可以在编译时就被解析。 + +### std::list 排序 +对 `std::list` 进行排序与对 `std::vector` 或者 `std::string` 的排序几乎类似的,除了 `std::list` 的排序方法是一个成员函数。 除了这个区别,`list::sort· 方法不接受范围,它有两个重载类型,一个是没有谓词,一个是有谓词。 +对于没有谓词的排序,默认是小于操作符 `operator <` 。 +```cpp +std::list numbers = { 0,9,1,2,8,4,6 }; +numbers.sort(); +``` +对于包含谓词的 `std::list::sort`, 我们可以基于 `lambda` 表达式去实现: +```cpp +auto sortYearPredicate = [](const car & a, const car & b) -> bool +{ + return a.year < b.year; +}; +cars.sort(sortYearPredicate); +``` + + +### 参考阅读 +[std::sort - cppreference.com](https://en.cppreference.com/w/cpp/algorithm/sort) + +[std::sort排序算法](https://blog.csdn.net/xijiacun/article/details/72902680) + +[C++ std::sort predicate with templates](https://studiofreya.com/cpp/cpp-std-sort-predicate-with-templates/) + + diff --git a/docs/2019/06/18/linux-dynamic-link/index.html b/docs/2019/06/18/linux-dynamic-link/index.html index e013602..7562bcc 100644 --- a/docs/2019/06/18/linux-dynamic-link/index.html +++ b/docs/2019/06/18/linux-dynamic-link/index.html @@ -377,7 +377,7 @@

相关文章

    - +
  • All Posts
  • diff --git a/docs/categories/index.html b/docs/categories/index.html index 9a78e1a..9e0ea9d 100644 --- a/docs/categories/index.html +++ b/docs/categories/index.html @@ -73,7 +73,7 @@

    Categories

  • 工具箱 5
  • -
  • 技术志 43
  • +
  • 技术志 44
  • 杂谈集 5
  • diff --git "a/docs/categories/\346\212\200\346\234\257\345\277\227/index.html" "b/docs/categories/\346\212\200\346\234\257\345\277\227/index.html" index 514da9b..fc94037 100644 --- "a/docs/categories/\346\212\200\346\234\257\345\277\227/index.html" +++ "b/docs/categories/\346\212\200\346\234\257\345\277\227/index.html" @@ -283,6 +283,23 @@

    +
    +
    + +

    + C++ 中基于谓词的 std::sort 介绍 +

    +
    +
    +

    数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 std::sort, 本篇主要介绍基于谓词的 std::sort 以及常见使用场景。

    +
    +
    +
    + + +
    diff --git a/docs/tags/c++/index.xml b/docs/tags/c++/index.xml index 5ae9f09..7a429c7 100644 --- a/docs/tags/c++/index.xml +++ b/docs/tags/c++/index.xml @@ -89,5 +89,14 @@ <p>STL 中容器的插入、删除、赋值都有相应的区间成员函数,相比于单元素的成员函数而言,使用区间成员函数使得代码更加清晰,更加高效。</p> + + C++ 中基于谓词的 std::sort 介绍 + https://maodanp.github.io/2019/07/05/std-sort-cpp/ + Fri, 05 Jul 2019 00:00:00 +0000 + + https://maodanp.github.io/2019/07/05/std-sort-cpp/ + <p>数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 <code>std::sort</code>, 本篇主要介绍基于谓词的 <code>std::sort</code> 以及常见使用场景。</p> + + diff --git a/docs/tags/index.html b/docs/tags/index.html index 217fd7e..5cbefa6 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -66,7 +66,7 @@

    Tags

    -
  • c++ 9
  • +
  • c++ 10
  • design-pattern 4
  • @@ -84,7 +84,7 @@

    Tags

  • security 4
  • -
  • stl 1
  • +
  • stl 2
  • 代理 2