Skip to content

Commit

Permalink
std::sort intruduction
Browse files Browse the repository at this point in the history
  • Loading branch information
damao committed May 20, 2022
1 parent c50e2f5 commit e9a42c2
Show file tree
Hide file tree
Showing 15 changed files with 307 additions and 6 deletions.
Binary file modified .DS_Store
Binary file not shown.
Binary file modified content/.DS_Store
Binary file not shown.
Binary file modified content/post/.DS_Store
Binary file not shown.
220 changes: 220 additions & 0 deletions content/post/2019/07/05/std-sort-cpp.md
Original file line number Diff line number Diff line change
@@ -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` 以及常见使用场景。

<!--more-->

### C++ 中的排序方法概述
C++ 中 `std::sort` 是一个标准的排序算法, 它的语法声明如下:
```cpp
template <class RandomAccessIterator>
void sort ( RandomAccessIterator first,RandomAccessIterator last );

template <class RandomAccessIterator,class Compare>
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 <algorithm>
#include <string>
#include <iostream>
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<some_data> 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<int> 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/)


2 changes: 1 addition & 1 deletion docs/2019/06/18/linux-dynamic-link/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ <h4 id="real-rels">相关文章</h4>
<ul class="pager">


<li class="previous"><a href="/2020/01/10/stl-range-methods/"><span aria-hidden="true">&larr;</span> Prev</a></li>
<li class="previous"><a href="/2019/07/05/std-sort-cpp/"><span aria-hidden="true">&larr;</span> Prev</a></li>


<li><a href="/post/">All Posts</a></li>
Expand Down
2 changes: 1 addition & 1 deletion docs/categories/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ <h1 id="title">Categories</h1>
<li><a href="/tags/%E5%B7%A5%E5%85%B7%E7%AE%B1/">工具箱</a> 5</li>


<li><a href="/tags/%E6%8A%80%E6%9C%AF%E5%BF%97/">技术志</a> 43</li>
<li><a href="/tags/%E6%8A%80%E6%9C%AF%E5%BF%97/">技术志</a> 44</li>


<li><a href="/tags/%E6%9D%82%E8%B0%88%E9%9B%86/">杂谈集</a> 5</li>
Expand Down
17 changes: 17 additions & 0 deletions docs/categories/技术志/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,23 @@ <h2 class="post-title">



<article class="post li">
<header>
<div class="post-date">
2019年07月05日
</div>
<h2 class="post-title">
<a href="/2019/07/05/std-sort-cpp/">C&#43;&#43; 中基于谓词的 std::sort 介绍</a>
</h2>
</header>
<div class="post-content">
<p>数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 <code>std::sort</code>, 本篇主要介绍基于谓词的 <code>std::sort</code> 以及常见使用场景。</p>
</div>
</article>
<hr>



<article class="post li">
<header>
<div class="post-date">
Expand Down
9 changes: 9 additions & 0 deletions docs/categories/技术志/index.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@
<description>&lt;p&gt;STL 中容器的插入、删除、赋值都有相应的区间成员函数,相比于单元素的成员函数而言,使用区间成员函数使得代码更加清晰,更加高效。&lt;/p&gt;</description>
</item>

<item>
<title>C&#43;&#43; 中基于谓词的 std::sort 介绍</title>
<link>https://maodanp.github.io/2019/07/05/std-sort-cpp/</link>
<pubDate>Fri, 05 Jul 2019 00:00:00 +0000</pubDate>

<guid>https://maodanp.github.io/2019/07/05/std-sort-cpp/</guid>
<description>&lt;p&gt;数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 &lt;code&gt;std::sort&lt;/code&gt;, 本篇主要介绍基于谓词的 &lt;code&gt;std::sort&lt;/code&gt; 以及常见使用场景。&lt;/p&gt;</description>
</item>

<item>
<title>Linux的动态链接</title>
<link>https://maodanp.github.io/2019/06/18/linux-dynamic-link/</link>
Expand Down
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ <h2 class="panel-title">Tags</h2>


<a href="/tags/c&#43;&#43;/" class="list-group-item">
<span class="badge">9</span>
<span class="badge">10</span>
c&#43;&#43;
</a>

Expand Down Expand Up @@ -151,7 +151,7 @@ <h2 class="panel-title">Tags</h2>
</a>

<a href="/tags/stl/" class="list-group-item">
<span class="badge">1</span>
<span class="badge">2</span>
stl
</a>

Expand Down
17 changes: 17 additions & 0 deletions docs/post/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,23 @@ <h2 class="post-title">



<article class="post li">
<header>
<div class="post-date">
2019年07月05日
</div>
<h2 class="post-title">
<a href="/2019/07/05/std-sort-cpp/">C&#43;&#43; 中基于谓词的 std::sort 介绍</a>
</h2>
</header>
<div class="post-content">
<p>数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 <code>std::sort</code>, 本篇主要介绍基于谓词的 <code>std::sort</code> 以及常见使用场景。</p>
</div>
</article>
<hr>



<article class="post li">
<header>
<div class="post-date">
Expand Down
9 changes: 9 additions & 0 deletions docs/post/index.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@
<description>&lt;p&gt;STL 中容器的插入、删除、赋值都有相应的区间成员函数,相比于单元素的成员函数而言,使用区间成员函数使得代码更加清晰,更加高效。&lt;/p&gt;</description>
</item>

<item>
<title>C&#43;&#43; 中基于谓词的 std::sort 介绍</title>
<link>https://maodanp.github.io/2019/07/05/std-sort-cpp/</link>
<pubDate>Fri, 05 Jul 2019 00:00:00 +0000</pubDate>

<guid>https://maodanp.github.io/2019/07/05/std-sort-cpp/</guid>
<description>&lt;p&gt;数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 &lt;code&gt;std::sort&lt;/code&gt;, 本篇主要介绍基于谓词的 &lt;code&gt;std::sort&lt;/code&gt; 以及常见使用场景。&lt;/p&gt;</description>
</item>

<item>
<title>Linux的动态链接</title>
<link>https://maodanp.github.io/2019/06/18/linux-dynamic-link/</link>
Expand Down
3 changes: 3 additions & 0 deletions docs/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
</url><url>
<loc>https://maodanp.github.io/2020/01/10/stl-range-methods/</loc>
<lastmod>2020-01-10T00:00:00+00:00</lastmod>
</url><url>
<loc>https://maodanp.github.io/2019/07/05/std-sort-cpp/</loc>
<lastmod>2019-07-05T00:00:00+00:00</lastmod>
</url><url>
<loc>https://maodanp.github.io/tags/linux/</loc>
<lastmod>2019-06-18T00:00:00+00:00</lastmod>
Expand Down
17 changes: 17 additions & 0 deletions docs/tags/c++/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,23 @@ <h2 class="post-title">
<hr>



<article class="post li">
<header>
<div class="post-date">
2019年07月05日
</div>
<h2 class="post-title">
<a href="/2019/07/05/std-sort-cpp/">C&#43;&#43; 中基于谓词的 std::sort 介绍</a>
</h2>
</header>
<div class="post-content">
<p>数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 <code>std::sort</code>, 本篇主要介绍基于谓词的 <code>std::sort</code> 以及常见使用场景。</p>
</div>
</article>
<hr>


</div>

</div>
Expand Down
9 changes: 9 additions & 0 deletions docs/tags/c++/index.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,14 @@
<description>&lt;p&gt;STL 中容器的插入、删除、赋值都有相应的区间成员函数,相比于单元素的成员函数而言,使用区间成员函数使得代码更加清晰,更加高效。&lt;/p&gt;</description>
</item>

<item>
<title>C&#43;&#43; 中基于谓词的 std::sort 介绍</title>
<link>https://maodanp.github.io/2019/07/05/std-sort-cpp/</link>
<pubDate>Fri, 05 Jul 2019 00:00:00 +0000</pubDate>

<guid>https://maodanp.github.io/2019/07/05/std-sort-cpp/</guid>
<description>&lt;p&gt;数据结构于算法中有很多种不同的排序算法,在 C++ 语言中,常见的排序排序算法是 &lt;code&gt;std::sort&lt;/code&gt;, 本篇主要介绍基于谓词的 &lt;code&gt;std::sort&lt;/code&gt; 以及常见使用场景。&lt;/p&gt;</description>
</item>

</channel>
</rss>
Loading

0 comments on commit e9a42c2

Please sign in to comment.