stl算法之sort 02

对于“排序”这个计算机科学里的经典问题,你是绝对没有必要自己写 for 循环的,必须坚决地选择标准算法。

在求职面试的时候,你也许手写过不少排序算法吧,像选择排序、插入排序、冒泡排序,等等,但标准库里的算法绝对要比你所能写出的任何实现都要好。说到排序,你脑海里跳出的第一个词可能就是 sort(),它是经典的快排算法,通常用它准没错。

使用方法可以参考我的另一篇文章stl算法之sort 01

不过,排序也有多种不同的应用场景,sort() 虽然快,但它是不稳定的,而且是全排所有元素。很多时候,这样做的成本比较高,比如 TopN、中位数、最大最小值等,我们只关心一部分数据,如果你用 sort(),就相当于“杀鸡用牛刀”,是一种浪费。

C++ 为此准备了多种不同的算法,不过它们的名字不全叫 sort,所以你要认真理解它们的含义。

我来介绍一些常见问题对应的算法:
1.要求排序后仍然保持元素的相对顺序,应该用 stable_sort,它是稳定的;
2.选出前几名(TopN),应该用 partial_sort;
3.选出前几名,但不要求再排出名次(BestN),应该用 nth_element;
4.中位数(Median)、百分位数(Percentile),还是用 nth_element;
5.按照某种规则把元素划分成两组,用 partition;
6.第一名和最后一名,用 minmax_element。

假设这样一种情境,有一个存有 100 万个元素的容器,但我们只想从中提取出值最小的 10 个元素,该如何实现呢?

通过我之前的文章,可能会想到使用 sort() 或者 stable_sort() 排序函数,即通过对容器中存储的 100 万个元素进行排序,就可以成功筛选出最小的 10 个元素。但仅仅为了提取 10 个元素,却要先对 100 万个元素进行排序,可想而知这种实现方式的效率是非常低的。

值得一提的是,stable_sort() 函数完全可以看作是 sort() 函数在功能方面的升级版。换句话说,stable_sort() 和 sort() 具有相同的使用场景,就连语法格式也是相同的(后续会讲),只不过前者在功能上除了可以实现排序,还可以保证不改变相等元素的相对位置。

要知道,一个函数的功能往往可以从它的函数名中体现出来,以 partial_sort() 函数为例,partial sort 可直译为“部分排序”。partial_sort() 函数的功能确是如此,即该函数可以从指定区域中提取出部分数据,并对它们进行排序。

但“部分排序”仅仅是对 partial_sort() 函数功能的一个概括,如果想彻底搞清楚它的功能,需要结合该函数的语法格式。partial_sort() 函数有 2 种用法,其语法格式分别为:

//按照默认的升序排序规则,对 [first, last) 范围的数据进行筛选并排序
void partial_sort (RandomAccessIterator first,
                   RandomAccessIterator middle,
                   RandomAccessIterator last);
//按照 comp 排序规则,对 [first, last) 范围的数据进行筛选并排序
void partial_sort (RandomAccessIterator first,
                   RandomAccessIterator middle,
                   RandomAccessIterator last,
                   Compare comp);

其中,first、middle 和 last 都是随机访问迭代器,comp 参数用于自定义排序规则。

partial_sort() 函数会以交换元素存储位置的方式实现部分排序的。具体来说,partial_sort() 会将 [first, last) 范围内最小(或最大)的 middle-first 个元素移动到 [first, middle) 区域中,并对这部分元素做升序(或降序)排序。

需要注意的是,partial_sort() 函数受到底层实现方式的限制,它仅适用于普通数组和部分类型的容器。换句话说,只有普通数组和具备以下条件的容器,才能使用 partial_sort() 函数:
1.容器支持的迭代器类型必须为随机访问迭代器。这意味着,partial_sort() 函数只适用于 array、vector、deque 这 3 个容器。

2.当选用默认的升序排序规则时,容器中存储的元素类型必须支持 <小于运算符;同样,如果选用标准库提供的其它排序规则,元素类型也必须支持该规则底层实现所用的比较运算符;

3.partial_sort() 函数在实现过程中,需要交换某些元素的存储位置。因此,如果容器中存储的是自定义的类对象,则该类的内部必须提供移动构造函数和移动赋值运算符。

接下来看看对10000个元素所花费的时间

void main()
{
	//sort函数是不稳定的,因为它有可能会改变原来元素的顺序,但stable_sort是稳定的,不会更改原有元素的顺序
	std::vector<int> m_vc{ 1, 2, 3, 2, 4, 6, 5 };
	m_vc.resize(10000);
	clock_t t1 = clock();
	std::stable_sort(m_vc.begin(), m_vc.end(), [](int x, int y){return x < y; });
	clock_t t2 = clock();
	cout << "\nstable_sort need time:" << t2 - t1 << endl;
	
	clock_t t3 = clock();
	//从小到大排序,将前四个放在最前面的四个位置
	std::partial_sort(m_vc.begin(), m_vc.begin() + 4, m_vc.end());
	clock_t t4 = clock();
	cout << "partial_sort need time:" << t4 - t3 << endl << endl;

	
	system("pause");
}

结果:
在这里插入图片描述
从运行结果可以看出部分排序确实比全部排完花费的时间更短。接下来我将元素放少一些,看看具体的使用。

void main()
{
	//sort函数是不稳定的,因为它有可能会改变原来元素的顺序,但stable_sort是稳定的,不会更改原有元素的顺序
	std::vector<int> m_vc{ 1, 2, 3, 2, 100,4, 6, 5 ,700,-1};
	cout << "原数组:";
	for (auto node :m_vc)
	{
		cout << node << " ";
	}
	cout << endl;
	
	std::partial_sort(m_vc.begin(), m_vc.begin() + 4, m_vc.end(), [](int x, int y){return x < y; });
	cout << "partial_sort(前面最小的四个元素) 后:";
	for (auto node :m_vc)
	{
		cout << node << " ";
		
	}
	cout << endl;

	std::vector<int> m_vcx{ 1, 2, 3, 2, 100, 4, 6, 5, 700, -1 };
	cout << "原数组:";
	for (auto node : m_vcx)
	{
		cout << node << " ";
	}
	cout << endl;
	std::partial_sort(m_vcx.begin(), m_vcx.begin() + 4, m_vcx.end(), [](int x, int y){return x > y; });
	cout << "partial_sort(前面最大的四个元素) 后:";
	for (auto node : m_vcx)
	{
		cout << node << " ";

	}
	cout << endl;
	
	system("pause");
}

结果:
在这里插入图片描述
那么你如果需要前面四个,你就可以遍历到第四个停止就OK了!

值得一提的是,partial_sort() 函数实现排序的平均时间复杂度为N*log(M),其中 N 指的是 [first, last) 范围的长度,M 指的是 [first, middle) 范围的长度。

partial_sort_copy() 函数的功能和 partial_sort() 类似,唯一的区别在于,前者不会对原有数据做任何变动,而是先将选定的部分元素拷贝到另外指定的数组或容器中,然后再对这部分元素进行排序。

partial_sort_copy() 函数也有 2 种语法格式,分别为:

//默认以升序规则进行部分排序
RandomAccessIterator partial_sort_copy (
                       InputIterator first,
                       InputIterator last,
                       RandomAccessIterator result_first,
                       RandomAccessIterator result_last);
//以 comp 规则进行部分排序
RandomAccessIterator partial_sort_copy (
                       InputIterator first,
                       InputIterator last,
                       RandomAccessIterator result_first,
                       RandomAccessIterator result_last,
                       Compare comp);

其中,first 和 last 为输入迭代器;result_first 和 result_last 为随机访问迭代器;comp 用于自定义排序规则。

partial_sort_copy() 函数会将 [first, last) 范围内最小(或最大)的 result_last-result_first 个元素复制到 [result_first, result_last) 区域中,并对该区域的元素做升序(或降序)排序。

值得一提的是,[first, last] 中的这 2 个迭代器类型仅限定为输入迭代器,这意味着相比 partial_sort() 函数,partial_sort_copy() 函数放宽了对存储原有数据的容器类型的限制。换句话说,partial_sort_copy() 函数还支持对 list 容器或者 forward_list 容器中存储的元素进行“部分排序”,而 partial_sort() 函数不行。

但是,介于 result_first 和 result_last 仍为随机访问迭代器,因此 [result_first, result_last) 指定的区域仍仅限于普通数组和部分类型的容器,这和 partial_sort() 函数对容器的要求是一样的。

看看怎么使用:

void main()
{
	//sort函数是不稳定的,因为它有可能会改变原来元素的顺序,但stable_sort是稳定的,不会更改原有元素的顺序
	std::vector<int> m_vc{ 1, 2, 3, 2, 100,4, 6, 5 ,700,-1};
	cout << "原数组:";
	for (auto node :m_vc)
	{
		cout << node << " ";
	}
	cout << endl;
	int a1[4] = { 0 };
	std::partial_sort_copy(m_vc.begin(), m_vc.end(), a1,a1+4);//默认是从小到大
	cout << "partial_sort_copy后得到的前面最小的四个元素:";
	for (auto node :a1)
	{
		cout << node << " ";
		
	}
	cout << endl;

	int a2[4] = { 0 };
	std::vector<int> m_vcx{ 1, 2, 3, 2, 100, 4, 6, 5, 700, -1 };
	cout << "原数组:";
	for (auto node : m_vcx)
	{
		cout << node << " ";
	}
	 
	cout << endl;
	std::partial_sort_copy(m_vcx.begin(), m_vcx.end(), a2,a2+4, [](int x, int y){return x > y; });
	cout << "partial_sort_copy前面最大的四个元素:";
	for (auto node : a2)
	{
		cout << node << " ";

	}
	cout << endl;
	
	system("pause");
}

结果:
在这里插入图片描述
值得一提的是,partial_sort_copy() 函数实现排序的平均时间复杂度为N*log(min(N,M)),其中 N 指的是 [first, last) 范围的长度,M 指的是 [result_first, result_last) 范围的长度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发如雪-ty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值