C++高阶-STL之容器元素的排序

sort()算法

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

sort()算法可以接受两个参数,分别用于指定需要进行排序的容器中数据的起始位置和结束位置。只要确定了排序的数据范围,默认情况下, sort()算法就会将数据元素按照从小到大的顺序排序,至于具体的排序和数据的移动,就无须我们操心了, sort()算法会处理好一切。

// 将 vecScore 容器中的学生成绩排序
sort(vecScore.begin(),vecScore.end());

默认情况下, sort()算法处理后的数据是按照从小到大的顺序排列的,如果想按照从大到小的顺序排列数据,则可以在容器排序完成之后,调用reverse()算法将容器中的所有数据翻转即可。例如:

// 将排序完成的容器进行翻转,以实现从大到小排序
reverse(vecScore.begin(), vecScore.end() );

sort()算法在排序的过程中,是通过“<”操作符对数据进行比较, 进而确定它们的大小关系进行排序的。这也就意味着,想要通过 sort()算法进行排序的数据,必须支持通过“<”比较大小。如果容器中保存的数据是自定义的数据类型,比如某个类的对象、某个结构体的变量等,就需要我们重载这个类型的“<”操作符,这样, sort()算法才可以使用它来比较容器中两个数据的大小,进而对所有数据进行排序。

例如,容器中保存的是自定义的表示矩形的 Rect 类型对象,我们希望按照矩形的面积对容器中的 Rect 对象进行排序,其代码如下:

// 创建表示矩形的 Rect 类
class Rect
{
	public:
		Rect() {}; // 默认构造函数
		// 构造函数,设定矩形的长和宽
		Rect( float fW, float fH )
		: m_fW(fW), m_fH(fH) {};
		// 重载“<”操作符,用于比较两个矩形的大小
		// 它的参数是另外一个 Rect 对象的引用
		bool operator < (const Rect& r ) const
		{
			// 计算两个矩形的面积
			float fArea = GetArea(); // 自身的面积
			// 另一个矩形的面积
			float fAreaOhter = r.GetArea();
			// 返回两个面积比较的结果作为两个矩形比较的结果
			return fArea < fAreaOhter;
		};
		// 获得矩形的面积
		float GetArea() const
		{
			return m_fW * m_fH;
		}
		// 获得矩形的宽和高
		float GetW() const
		{
			return m_fW;
		}
		float GetH() const
		{
			return m_fH;
		}
		// 获取矩形的周长
		float GetGirth() const
		{
			return (GetW() + GetH()) * 2;
		}
	// 矩形的属性:长和宽
	private:
		float m_fW;
		float m_fH;
};
// 创建保存 Rect 对象的容器
vector<Rect> vecRect;
// 将多个 Rect 对象添加到容器中
vecRect.push_back( Rect(3, 4) );
vecRect.push_back( Rect(6, 7) );
vecRect.push_back( Rect(8, 1) );
// 对容器中的 Rect 对象进行排序
// 这里使用默认的“<”操作符,按照面积排序
sort( vecRect.begin(), vecRect.end() );

我们对容器中的数据进行排序,不只是让数据按照某种顺序排列就结束了,更多时候,我们还会在容器中的数据排序完成之后,对其进行进一步的处理以获得更多更丰富的结果。比如,针对排序完成的容器,

  • 可以使用 lower_bound()算法和 upper_bound()算法获得容器中小于某个值或者大于某个值的临界点位置
  • 可以使用 equal_range()算法获得容器中所有等于某个值的数据范围

例如,统计出所有及格的学生总人数及刚好及格的学生人数:

// 汇总学生成绩并排序…
// 获取容器中及格分数(大于临界值 59)的临界点,
// upper_bound()算法返回的是指向这个临界点的迭代器
auto itpass =
upper_bound(vecScore.begin(), vecScore.end(), // 容器的数据范围
59); // 临界值// 获取容器中刚好及格( 60)的数据范围
// equal_range()算法返回的是一个 pair 对象,
// 它的 first 成员保存的是这个范围的起始位置,
// 而它的 second 成员保存的是这个范围的结束位置
auto passScores = equal_range(vecScore.begin(),
vecScore.end(), // 容器的数据范围
60); // 及格分数
// 输出数据处理的结果
// 输出所有及格的分数
cout<<"所有及格的分数是: "<<endl;
for( auto it = itpass; it != vecScore.end(); ++it )
	cout<<*it<<endl;
// 利用迭代器计算符合条件的数据的个数
cout<<"所有及格的学生人数: "<<int(vecScore.end() - itpass)<<endl;
cout<<"刚好及格的学生人数: "<<int(passScores.second - passScores.first)<<endl;

在这里,我们利用 equal_range()返回的迭代器来计算容器中符合条件的数据的数目,应该只是equal_range()算法的一个副产品。
实际上, STL 提供了count()算法和 count_if()算法,专门用于统计容器中符合某个条件的数据的个数。 利用这两个算法,上面的代码还可以进一步“优雅”为:

// 统计容器中所有及格的分数个数
// count_if()算法使用 ispass()函数判断当前数据是否属于及格分数,
// 如果 ispass()函数返回的是 true,则当前数据属于及格分数统计在内
int nAllPass = count_if( vecScore.begin(),
vecScore.end(), // 容器数据范围
ispass); // 判断函数
cout<<"所有及格的学生人数: "<<nAllPass<<endl;
// 统计刚好及格的学生人数
// 也就是利用 count()算法统计容器中有多少个值为 60 的数据
int nPass = count( vecScore.begin(),
vecScore.end(), // 容器数据范围
60); // 被统计的数值
cout<<"刚好及格的学生人数: "<<nPass<<endl;

自定义排序规则

第二个版本的 sort()算法在第一个版本的基础上增加了一个参数 comp,而正是这个增加的参数使我们可以对排序的规则进行自定义。这个参数是一个函数,而在这个函数中可以自定义排序的比较规则,所以这个函数也称为比较函数。这个比较函数有两个与待比较数据类型相同的输入参数和一个 bool 类型的返回值。

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

例如,可以利用 sort()算法中的比较函数重新定义 vecRect 容器中 Rect 对象的排序规则,让它们按照矩形的长进行排序:

// 定义比较函数,实现自己的比较规则
// 比较函数的两个参数的数据类型就是容器中数据的类型,而返回值类型是 bool 类型
bool sortbyH( const Rect& rect1, const Rect& rect2 )
{
	// 在比较函数中,我们定义具体的比较规则,
	// 这里比较两个矩形的长,以矩形长的比较结果作为矩形的比较结果
	// 默认情况下,数据是按照“<”操作符比较后的结果进行排序的,
	// 所以我们这里也返回“<”操作符比较的结果
	return rect1.GetH() < rect2.GetH();
}
// 使用自定义的比较规则代替默认的比较规则对容器中的数据进行排序
sort( vecRect.begin(), vecRect.end(), sortbyH );

sort()算法提供的只是一个排序算法的框架,它负责维护排序算法的整个过程,例如,先从容器中取出两个数据,然后进行比较,最后根据比较的结果将数据放到容器的合适位置。但是,它将排序算法中最核心最关键的部分——比较规则的定义留给了我们,让我们可以对比较规则进行自定义,以此实现同一个固定的算法框架,应用不同的自定义比较规则处理不同类型的数据,最终达到算法的通吃天下。

max_element()和 min_element()算法

它们的使用同 sort()算法非常相似,只需要用一对迭代器指定查找的范围即可。例如,可以利用这两个算法方便地获得 vecRect 容器中面积最大和最小的矩形:

// 利用 max_element()算法获取 vecRect 容器中的最大值,也就是面积最大的矩形
// max_element()算法返回的是指向这个最大值的迭代器
auto maxit = max_element( vecRect.begin(),
vecRect.end() ); // 参数是两个迭代器表示的查找范围
// 通过指向这个最大值的迭代器访问最大值
Rect maxRect = *(maxit);
// 利用 min_element()算法获取 vecRect 容器中的最小值
auto minit = min_element( vecRect.begin(),
vecRect.end() );
Rect minRect = *(minit);

跟排序算法一样,我们也可以用一个函数作为这两个算法的第三个参数,从而对比较规则进行重新定义,实现算法的自定义。例如,我们想要获得 vecRect 容器中周长最长的矩形:

// 比较两个矩形的周长,对比较规则进行重新定义
bool maxbyGirth( const Rect& rect1, const Rect& rect2 )
{
	return rect1.GetGirth() < rect2.GetGirth();
}
// 获取 vecRect 容器中周长最长的矩形
auto maxgirthit = max_element( vecRect.begin(),
vecRect.end(),
maxbyGirth ); // 比较 Rect 对象的周长
Rect maxGirthRect = *(maxgirthit); // 获得周长最长的矩形

完整代码:

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

using namespace std;

class Rect
{
    public:
        Rect() {};
        Rect(float fW, float fH):m_fW(fW), m_fH(fH) {};
        bool operator < (const Rect& r) const
        {
            float fArea = GetArea();
            float fAreaOther = r.GetArea();
            return fArea < fAreaOther;
        };
        float GetArea() const
        {
            return m_fW * m_fH;
        }
        float GetW() const
        {
            return m_fW;
        }
        float GetH() const
        {
            return m_fH;
        }
        float GetGirth() const
        {
            return (GetH() + GetW()) * 2;
        }

    private:
        float m_fW;
        float m_fH;

};

bool sortByH(const Rect& rect1, const Rect& rect2)
{
    return rect1.GetH() < rect2.GetH();
}

bool maxByGirth(const Rect& rect1, const Rect& rect2)
{
    return rect1.GetGirth() < rect2.GetGirth();
}

int main()
{
    vector<Rect> vecRect;
    vecRect.push_back(Rect(3, 4));
    vecRect.push_back(Rect(6, 7));
    vecRect.push_back(Rect(8, 9));
    sort(vecRect.begin(), vecRect.end(), sortByH);
    for(auto temp:vecRect){
        cout<<temp.GetArea()<<endl;
    }
    auto maxit = max_element(vecRect.begin(), vecRect.end());
    Rect maxRect = *(maxit);
    cout<<maxRect.GetW() <<' '<< maxRect.GetH()<<endl;
    auto minit = min_element(vecRect.begin(), vecRect.end());
    Rect minRect = *(minit);
    cout<<minRect.GetW() <<' '<< minRect.GetH()<<endl;
    auto maxgirthit = max_element(vecRect.begin(), vecRect.end(), maxByGirth);
    Rect maxGirthRect = * (maxgirthit);
    cout << maxGirthRect.GetGirth()<<endl;
    return 0;
}

output:
12
42
72
8 9
3 4
34
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值