MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++ STL 算法 sort 的自定义排序规则

2023-01-147.2k 阅读

C++ STL 算法 sort 的自定义排序规则

在 C++ 的标准模板库(STL)中,sort 算法是一个非常实用的工具,用于对给定范围内的元素进行排序。默认情况下,sort 使用的是升序排序。然而,在实际编程中,我们常常需要根据特定的需求定义自己的排序规则。本文将深入探讨如何在 sort 算法中使用自定义排序规则。

1. sort 算法简介

sort 函数定义在 <algorithm> 头文件中,它有两个重载版本。最常用的版本接受两个迭代器,分别指向要排序范围的起始和结束位置(不包括结束位置的元素)。例如:

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

int main() {
    std::vector<int> numbers = {5, 3, 7, 1, 9};
    std::sort(numbers.begin(), numbers.end());

    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

上述代码将 numbers 向量中的元素按升序排序并输出。默认情况下,sort 使用的是快速排序算法的一种变体,平均时间复杂度为 $O(n \log n)$,其中 n 是要排序的元素个数。

2. 自定义排序函数

为了使用自定义排序规则,我们需要提供一个比较函数。这个比较函数应该接受两个参数,并返回一个布尔值,表示第一个参数是否应该排在第二个参数之前。

2.1 函数指针作为比较函数

我们可以定义一个普通函数,并将其指针作为 sort 的第三个参数传递。例如,假设我们要对整数按降序排序:

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

bool compareDescending(int a, int b) {
    return a > b;
}

int main() {
    std::vector<int> numbers = {5, 3, 7, 1, 9};
    std::sort(numbers.begin(), numbers.end(), compareDescending);

    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

在上述代码中,compareDescending 函数比较两个整数 ab,如果 a 大于 b,则返回 true,表示 a 应该排在 b 之前,从而实现降序排序。

2.2 仿函数作为比较函数

仿函数(functor)是一种行为类似函数的对象,通过重载 () 运算符实现。使用仿函数的优点是可以在对象中存储状态,这在某些复杂的排序场景中非常有用。

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

class CompareDescending {
public:
    bool operator()(int a, int b) const {
        return a > b;
    }
};

int main() {
    std::vector<int> numbers = {5, 3, 7, 1, 9};
    std::sort(numbers.begin(), numbers.end(), CompareDescending());

    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

这里定义了 CompareDescending 类,重载了 () 运算符。在 sort 调用中,创建了 CompareDescending 的临时对象作为比较函数。

3. 自定义类型的排序

当对自定义类型进行排序时,自定义排序规则变得尤为重要。假设我们有一个表示点的结构体 Point,我们可能希望根据点到原点的距离进行排序。

3.1 结构体定义与距离计算

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>

struct Point {
    int x;
    int y;

    double distanceFromOrigin() const {
        return std::sqrt(x * x + y * y);
    }
};

3.2 使用函数指针进行排序

bool compareByDistance(const Point& a, const Point& b) {
    return a.distanceFromOrigin() < b.distanceFromOrigin();
}

int main() {
    std::vector<Point> points = {
        {3, 4},
        {1, 1},
        {5, 0}
    };

    std::sort(points.begin(), points.end(), compareByDistance);

    for (const Point& point : points) {
        std::cout << "(" << point.x << ", " << point.y << ") ";
    }
    return 0;
}

在上述代码中,compareByDistance 函数比较两个 Point 对象到原点的距离,sort 函数根据这个规则对 points 向量进行排序。

3.3 使用仿函数进行排序

class CompareByDistance {
public:
    bool operator()(const Point& a, const Point& b) const {
        return a.distanceFromOrigin() < b.distanceFromOrigin();
    }
};

int main() {
    std::vector<Point> points = {
        {3, 4},
        {1, 1},
        {5, 0}
    };

    std::sort(points.begin(), points.end(), CompareByDistance());

    for (const Point& point : points) {
        std::cout << "(" << point.x << ", " << point.y << ") ";
    }
    return 0;
}

这里通过 CompareByDistance 仿函数实现了同样的排序功能。

4. 复杂自定义排序规则

有时候,我们的排序规则可能会更加复杂。例如,对于字符串,我们可能希望先按长度排序,如果长度相同,再按字典序排序。

4.1 字符串排序示例

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

bool compareStrings(const std::string& a, const std::string& b) {
    if (a.length() != b.length()) {
        return a.length() < b.length();
    } else {
        return a < b;
    }
}

int main() {
    std::vector<std::string> words = {
        "apple",
        "banana",
        "cherry",
        "date",
        "fig"
    };

    std::sort(words.begin(), words.end(), compareStrings);

    for (const std::string& word : words) {
        std::cout << word << " ";
    }
    return 0;
}

compareStrings 函数中,首先比较字符串的长度,如果长度不同,较短的字符串排在前面;如果长度相同,则按字典序排序。

4.2 结合多个条件的结构体排序

假设我们有一个 Student 结构体,包含姓名、年龄和成绩,我们希望先按成绩降序排序,如果成绩相同,再按年龄升序排序,如果年龄也相同,再按姓名字典序排序。

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

struct Student {
    std::string name;
    int age;
    int score;
};

bool compareStudents(const Student& a, const Student& b) {
    if (a.score != b.score) {
        return a.score > b.score;
    } else if (a.age != b.age) {
        return a.age < b.age;
    } else {
        return a.name < b.name;
    }
}

int main() {
    std::vector<Student> students = {
        {"Alice", 20, 85},
        {"Bob", 19, 90},
        {"Charlie", 20, 85},
        {"David", 18, 95}
    };

    std::sort(students.begin(), students.end(), compareStudents);

    for (const Student& student : students) {
        std::cout << student.name << " " << student.age << " " << student.score << std::endl;
    }
    return 0;
}

compareStudents 函数中,通过多层条件判断实现了复杂的排序规则。

5. Lambda 表达式作为比较函数

C++11 引入了 lambda 表达式,使得定义简单的比较函数变得更加简洁。

5.1 整数降序排序

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

int main() {
    std::vector<int> numbers = {5, 3, 7, 1, 9};
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });

    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

这里使用 lambda 表达式 [](int a, int b) { return a > b; } 定义了降序比较函数。

5.2 自定义结构体排序

对于前面的 Student 结构体,我们也可以使用 lambda 表达式进行排序。

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

struct Student {
    std::string name;
    int age;
    int score;
};

int main() {
    std::vector<Student> students = {
        {"Alice", 20, 85},
        {"Bob", 19, 90},
        {"Charlie", 20, 85},
        {"David", 18, 95}
    };

    std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
        if (a.score != b.score) {
            return a.score > b.score;
        } else if (a.age != b.age) {
            return a.age < b.age;
        } else {
            return a.name < b.name;
        }
    });

    for (const Student& student : students) {
        std::cout << student.name << " " << student.age << " " << student.score << std::endl;
    }
    return 0;
}

通过 lambda 表达式,我们可以在 sort 调用的地方直接定义复杂的比较逻辑,使代码更加紧凑。

6. 稳定性与自定义排序

排序算法的稳定性是指相等元素在排序前后的相对顺序保持不变。sort 算法默认是不稳定的。然而,在某些情况下,稳定性非常重要。例如,在对具有相同成绩的学生进行排序时,如果我们希望保持他们原来的顺序,就需要使用稳定的排序算法。

stable_sort 函数也定义在 <algorithm> 头文件中,它保证了相等元素的相对顺序。使用自定义排序规则的方式与 sort 类似。

6.1 stable_sort 示例

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

struct Student {
    std::string name;
    int age;
    int score;
};

bool compareStudents(const Student& a, const Student& b) {
    return a.score < b.score;
}

int main() {
    std::vector<Student> students = {
        {"Alice", 20, 85},
        {"Bob", 19, 85},
        {"Charlie", 20, 90},
        {"David", 18, 90}
    };

    std::stable_sort(students.begin(), students.end(), compareStudents);

    for (const Student& student : students) {
        std::cout << student.name << " " << student.age << " " << student.score << std::endl;
    }
    return 0;
}

在上述代码中,stable_sort 保证了成绩相同的学生(如 AliceBobCharlieDavid)的相对顺序不变。

7. 性能考虑

虽然 sort 算法的平均时间复杂度为 $O(n \log n)$,但在实际应用中,性能还可能受到比较函数的复杂度影响。如果比较函数的时间复杂度较高,整个排序过程的性能也会受到影响。

例如,在比较自定义结构体时,如果比较函数涉及复杂的计算,如大量的数学运算或字符串操作,可能会导致排序性能下降。在这种情况下,我们可以考虑提前计算一些值并存储在结构体中,以简化比较函数。

另外,对于大规模数据的排序,stable_sort 虽然保证了稳定性,但通常比 sort 性能略低,因为它需要更多的空间来维护相等元素的顺序。因此,在选择排序算法时,需要根据数据特点和需求权衡稳定性和性能。

8. 总结与实践建议

通过自定义排序规则,我们可以让 sort 算法满足各种复杂的排序需求。无论是简单的升序、降序,还是涉及自定义类型和多条件的复杂排序,都可以通过函数指针、仿函数或 lambda 表达式来实现。

在实际编程中,建议根据具体情况选择合适的方式定义比较函数。如果比较函数逻辑简单,使用 lambda 表达式可以使代码更简洁;如果需要存储状态或复用比较逻辑,仿函数是一个不错的选择;而函数指针则是一种传统且通用的方式。

同时,要注意排序算法的稳定性和性能。对于需要保持相等元素相对顺序的场景,选择 stable_sort;对于性能要求较高且稳定性不重要的场景,sort 通常是更好的选择。

通过深入理解和熟练运用 sort 算法的自定义排序规则,我们能够更高效地处理各种数据排序任务,提升程序的灵活性和实用性。

希望本文对您理解和使用 C++ STL 中 sort 算法的自定义排序规则有所帮助。在实际编程中不断实践,您将能够更加熟练地运用这一强大的工具。