二维数组-分组排序,多重排序 也称为复合排序

19 阅读4分钟

背景

实际应用示例

  • 电子表格软件(如Excel)

    • 选择要排序的数据区域。
    • 打开“自定义排序”或“排序”对话框。 
    • 在“排序依据”下拉列表中,选择第一个需要排序的列(例如,“班级”)。
    • 在“次序”下拉列表中选择排序方式(例如,“升序”)。 
    • 点击“添加”按钮,添加第二个排序依据(例如,“姓名”),并设置排序方式。
    • 重复此步骤,直到所有需要排序的列都添加完毕,然后单击“确定”。 
  • SQL 数据库

    • 在SQL 语句中使用 ORDER BY 子句,并列出多个字段来指定多重排序的顺序。 
    • 例如,ORDER BY 班级ASC, 姓名DESC 表示首先按“班级”升序排序,然后在班级相同的情况下,再按“姓名”降序排序。 
  • 文献排序

    • 在参考文献列表中,通常会先按作者姓氏拼音排序,如果作者姓氏相同,则按作者名字拼音排序;如果作者姓名都相同,则按出版年份排序,依此类推

实现原理

在Java 中对二维数组进行分组排序,可以使用 Arrays.sort() 方法结合自定义 Comparator 或Lambda 表达式来实现,具体取决于分组依据。 你可以定义一个比较器,指定比较两个子数组的规则,例如先按第一列排序,如果第一列相同,则按第二列排序。 

使用 Arrays.sort() 和 Comparator 

这种方法通过实现一个自定义比较器来控制排序逻辑

import java.util.Arrays;
import java.util.Comparator;

public class Sort2DArray {
    public static void main(String[] args) {
        int[][] arr = {
            {1, 5},
            {3, 2},
            {1, 3},
            {2, 8}
        };

        // 按照第一列升序,第一列相同则按第二列升序排序
        Arrays.sort(arr, new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                // 比较第一列
                if (a[0] != b[0]) {
                    return Integer.compare(a[0], b[0]);
                } else {
                    // 如果第一列相同,比较第二列
                    return Integer.compare(a[1], b[1]);
                }
            }
        });

        // 打印排序后的数组
        for (int[] row : arr) {
            System.out.println(Arrays.toString(row));
        }
    }
}

使用 Arrays.sort() 和Lambda 表达式 

对于Java 8 及以上版本,可以使用Lambda 表达式简化 Comparator 的实现,使代码更简洁

import java.util.Arrays;

public class Sort2DArrayLambda {
    public static void main(String[] args) {
        int[][] arr = {
            {1, 5},
            {3, 2},
            {1, 3},
            {2, 8}
        };

        // 使用 Lambda 表达式实现自定义比较器
        Arrays.sort(arr, (a, b) -> {
            if (a[0] != b[0]) {
                return Integer.compare(a[0], b[0]);
            } else {
                return Integer.compare(a[1], b[1]);
            }
        });

        // 打印排序后的数组
        for (int[] row : arr) {
            System.out.println(Arrays.toString(row));
        }
    }
}

自定义调整: 我可以看到,仅需调整a和b的比较顺序,就可以轻松实现多组排序的升序/降序,先比较数据,后比较数据 首先根据第一个关键字段进行排序,当第一个字段的值相同时,再根据第二个关键字段进行排序,以此类推。 这种方法常用于对包含多个信息列的数据进行排序,以实现更精细化的数据组织

层层递进: 如果第二层排序结果中仍有相同的值,则会继续按照你指定的第三个、第四个(依此类推)字段进行排序,直到所有排序键都比较完毕

应用场景

生活中很多排序也会使用多组排序的思路,例如,学生成绩总分排序,成绩一致情况,按照学号排序 算法题目中涉及多个属性问题,可能会使用到,如下,我们举例一个算法题目帮助大家理解

leetcode 354. 俄罗斯套娃信封问题 在这道题目中,我们对于信封的两个数据 宽度和高度 进行分组排序

import java.util.*;

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        if (envelopes == null || envelopes.length == 0) {
            return 0;
        }
        
        // 使用Lambda表达式简化排序
        Arrays.sort(envelopes, (a, b) -> a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]);
        
        List<Integer> g = new ArrayList<>();
        for (int[] envelope : envelopes) {
            int h = envelope[1];
            
            // 使用Collections.binarySearch
            int index = Collections.binarySearch(g, h);
            if (index < 0) {
                index = -(index + 1);
            }
            
            if (index == g.size()) {
                g.add(h);
            } else {
                g.set(index, h);
            }
        }
        
        return g.size();
    }
}

算法思路解释:

  1. 排序策略:先按宽度升序,宽度相同时按高度降序

    • 这样确保在相同宽度的信封中,我们只会选择其中一个(因为高度是降序的)
    • 将二维问题转化为一维的高度序列的最长递增子序列问题
  2. 最长递增子序列(LIS)

    • 使用贪心 + 二分查找的方法
    • 维护一个列表 g,其中 g[i] 表示长度为 i+1 的递增子序列的最小末尾值
    • 对于每个高度,找到它在 g 中的插入位置并更新

时间复杂度:O(n log n)
空间复杂度:O(n)

这种方法巧妙地利用排序将二维问题降为一维,然后使用经典的LIS算法求解