今天遇到一个典型的二维数组排序问题,需要将原本的升序排列改为降序排列。这类问题在实际开发中经常遇到,特别是在处理需要自定义排序规则的数据结构时。让我们先看看原始代码的结构和问题所在。
原始代码定义了一个名为fufu的结构体,包含两个整型成员h和w,并重载了<运算符来实现比较逻辑。当前的比较逻辑是先比较h,如果h相同再比较w,都是按照升序排列的。
cpp复制struct fufu {
int h;
int w;
bool operator<(fufu other) {
if (h != other.h)
return h < other.h;
else
return w < other.w;
}
};
升序和降序排序的本质区别在于比较运算符的方向。在C++中,标准库的排序算法和大多数自定义排序都是基于<运算符实现的。当我们需要改变排序方向时,实际上只需要改变比较逻辑的返回结果。
对于升序排序,我们通常使用a < b这样的比较;而对于降序排序,则需要使用a > b。这个原理同样适用于自定义数据结构的排序。
在原始代码中,比较运算符的实现是:
cpp复制return h < other.h; // 升序
return w < other.w; // 升序
要实现降序排序,只需要将<改为>即可:
cpp复制return h > other.h; // 降序
return w > other.w; // 降序
让我们看看修改后的完整代码实现。除了比较运算符的修改外,我还对代码做了一些优化和注释,使其更加清晰易懂。
cpp复制#include <iostream>
#include <algorithm> // 引入swap函数
// 定义结构体,包含两个整型成员h和w
struct fufu {
int h;
int w;
// 重载<运算符,实现降序比较
bool operator<(const fufu& other) const {
if (h != other.h)
return h > other.h; // h降序
else
return w > other.w; // h相同时,w降序
}
};
int main() {
int n, sum = 0;
std::cin >> n;
fufu s[100];
// 输入数据,注意数组索引从0开始更符合C++习惯
for (int i = 0; i < n; i++)
std::cin >> s[i].h >> s[i].w;
// 冒泡排序实现
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (s[j+1] < s[j]) { // 使用重载的<运算符
std::swap(s[j], s[j+1]);
sum++;
}
}
}
std::cout << sum << std::endl;
return 0;
}
运算符重载优化:
const修饰符,确保比较操作不会修改对象状态const fufu&避免不必要的拷贝数组索引调整:
排序逻辑优化:
<运算符实现降序比较虽然冒泡排序简单易懂,但在实际应用中,我们通常会选择更高效的排序算法。C++标准库提供了std::sort函数,可以更方便地实现自定义排序。
cpp复制#include <algorithm>
#include <iostream>
struct fufu {
int h;
int w;
// 重载<运算符
bool operator<(const fufu& other) const {
if (h != other.h)
return h > other.h;
return w > other.w;
}
};
int main() {
int n;
std::cin >> n;
fufu s[100];
for (int i = 0; i < n; i++)
std::cin >> s[i].h >> s[i].w;
// 使用标准库排序
std::sort(s, s + n);
// 输出排序结果
for (int i = 0; i < n; i++)
std::cout << s[i].h << " " << s[i].w << std::endl;
return 0;
}
冒泡排序:
std::sort:
提示:在实际项目中,除非有特殊需求,否则建议优先使用标准库提供的排序算法。
在实现自定义排序时,经常会遇到一些问题。下面总结了一些常见问题及其解决方法。
问题现象:排序结果不符合预期,可能出现乱序或崩溃。
解决方法:
问题现象:程序运行时崩溃或产生不可预测的结果。
解决方法:
问题现象:处理大规模数据时速度很慢。
解决方法:
在实际应用中,我们经常需要对多个字段进行复杂排序。下面介绍几种常见的多字段排序策略。
当需要按照多个字段的不同优先级排序时,可以在比较函数中依次比较各个字段:
cpp复制bool operator<(const fufu& other) const {
if (h != other.h)
return h > other.h; // 第一优先级:h降序
if (w != other.w)
return w < other.w; // 第二优先级:w升序
return false; // 所有字段都相等
}
有时我们需要更灵活的排序规则,可以通过单独定义比较函数来实现:
cpp复制bool compareFufu(const fufu& a, const fufu& b) {
if (a.h != b.h)
return a.h > b.h;
return a.w > b.w;
}
// 使用方式
std::sort(s, s + n, compareFufu);
C++11引入了lambda表达式,可以更灵活地定义排序规则:
cpp复制std::sort(s, s + n, [](const fufu& a, const fufu& b) {
if (a.h != b.h)
return a.h > b.h;
return a.w > b.w;
});
让我们看一个实际应用场景:学生成绩排序。假设我们需要按照总分降序排序,总分相同则按照语文成绩降序排序。
cpp复制struct Student {
std::string name;
int chinese;
int math;
int english;
int total() const { return chinese + math + english; }
bool operator<(const Student& other) const {
if (total() != other.total())
return total() > other.total();
return chinese > other.chinese;
}
};
void sortStudents() {
Student students[100];
int n;
std::cin >> n;
for (int i = 0; i < n; i++) {
std::cin >> students[i].name
>> students[i].chinese
>> students[i].math
>> students[i].english;
}
std::sort(students, students + n);
for (int i = 0; i < n; i++) {
std::cout << students[i].name << " "
<< students[i].total() << std::endl;
}
}
对于需要频繁排序的场景,可以考虑以下优化策略:
预计算排序键:如果排序基于计算得到的值(如总分),可以预先计算并存储,避免每次比较都重新计算。
使用稳定排序:当需要保持相等元素的原始顺序时,使用std::stable_sort。
减少拷贝开销:对于大型对象,考虑存储指针或使用移动语义。
并行排序:对于大规模数据,可以使用并行排序算法如std::execution::par策略。
cpp复制#include <execution>
std::sort(std::execution::par, s, s + n);
编写完排序代码后,必须进行充分的测试。以下是一些测试用例的建议:
基本测试:
边界测试:
随机测试:
cpp复制void testSort() {
fufu test1[] = {{5,1}, {60,3}, {70,2}, {80,4}, {55,4}, {50,4}};
int n = sizeof(test1)/sizeof(test1[0]);
std::sort(test1, test1 + n);
for (int i = 0; i < n-1; i++) {
assert(!(test1[i+1] < test1[i])); // 验证排序正确性
}
}
虽然本文以C++为例,但自定义排序的概念在其他语言中同样适用。以下是几种常见语言的实现方式:
python复制class Fufu:
def __init__(self, h, w):
self.h = h
self.w = w
def __lt__(self, other):
if self.h != other.h:
return self.h > other.h
return self.w > other.w
# 使用示例
data = [Fufu(5,1), Fufu(60,3), Fufu(70,2)]
data.sort()
java复制class Fufu implements Comparable<Fufu> {
int h, w;
public Fufu(int h, int w) {
this.h = h;
this.w = w;
}
@Override
public int compareTo(Fufu other) {
if (h != other.h)
return Integer.compare(other.h, h); // 降序
return Integer.compare(other.w, w); // 降序
}
}
// 使用示例
List<Fufu> list = Arrays.asList(new Fufu(5,1), new Fufu(60,3));
Collections.sort(list);
javascript复制const data = [{h:5,w:1}, {h:60,w:3}];
data.sort((a, b) => {
if (a.h !== b.h) return b.h - a.h; // 降序
return b.w - a.w; // 降序
});
通过这个案例,我们可以总结出一些自定义排序的最佳实践:
明确排序需求:在开始编码前,明确排序的字段和顺序(升序/降序)
保持比较一致性:确保比较运算符实现严格弱序关系
优先使用标准库:除非有特殊需求,否则优先使用语言提供的排序函数
考虑性能因素:根据数据规模选择合适的算法
充分测试:编写全面的测试用例,包括边界情况
代码可读性:为自定义排序逻辑添加清晰的注释
灵活运用多种方法:根据场景选择运算符重载、比较函数或lambda表达式
在实际开发中,自定义排序是一个非常实用的技能。掌握好这个技巧,可以让你更高效地处理各种数据排序需求。