Java 高效地合并和重新排序已排序的列表

Java 高效地合并和重新排序已排序的列表,java,algorithm,sorting,merge,time-complexity,Java,Algorithm,Sorting,Merge,Time Complexity,这不是经典的“合并两个排序的”问题列表,这是在线性时间内完成的 我试图做的是合并两个(键,值)对列表,它们已经按值排序,其中两个列表中都有具有相同键的对象:这些对象应该合并(添加)它们的值,这可能会改变它们的排序顺序。我主要感兴趣的是如何使用已经排序的列表中的信息高效地执行排序,因为排序是该算法中最慢的部分 让我们举一个具体的例子。想象一个列表的学生对象: class Student { final String name; final int score; ... } 给定两个按

这不是经典的“合并两个排序的”问题列表,这是在线性时间内完成的

我试图做的是合并两个
(键,值)
对列表,它们已经按
排序,其中两个列表中都有具有相同
的对象:这些对象应该合并(添加)它们的
值,这可能会改变它们的排序顺序。我主要感兴趣的是如何使用已经排序的列表中的信息高效地执行排序,因为排序是该算法中最慢的部分

让我们举一个具体的例子。想象一个
列表
学生
对象:

class Student {
  final String name;
  final int score;
  ...
}
给定两个按
分数排序的
列表
,作为输入,我想创建一个新的合并学生列表,其中两个列表中出现的任何学生(由
student.name
标识)在最终列表中出现一次,分数等于两个列表中的分数之和。原始列表应保持不变

public class Student {

        String name = "";
        int score = 0;

        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }

        @Override
        public boolean equals(Object v) {
            if (v instanceof Student) {
                return this.name.equals(((Student) v).name);
            } else if (v instanceof String) {
                return this.name.equals(String.valueOf(v));
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 67 * hash + Objects.hashCode(this.name);
            return hash;
        }
    }
例如:

List 1:
{"bob", 20}
{"john", 15}
{"mark", 14}

List 2:
{"bill", 11}
{"mark", 9}
{"john", 1}

Result:
{"mark", 23}
{"bob", 20}
{"john", 16}
{"bill", 11}
合并本身(识别出现在两个列表中的学生)可以使用任何O(1)查找/插入结构(如
HashMap
)在预期的O(1)时间内完成。我最感兴趣的是排序步骤(尽管我不排除同时进行合并和排序的解决方案)

但问题是,我如何有效地重新排序这样的列表?现有列表的排序清楚地限制了元素在合并列表中的最终位置。例如,如果一名学生在第一个列表中的位置为
i
,在第二个列表中的位置为
j
,则他必须通过一个简单的参数出现在合并列表中的第一个
i+j
学生中,该参数分析可能具有更高分数的学生的最大数量。然而,目前还不清楚这些信息是否有助于对列表进行排序

你可以假设在许多情况下,在一个列表中得分高的学生在另一个列表中得分高。如果不是这样的话,该算法应该可以工作,但是除了列表已经排序这一事实之外,它还为您提供了一些关于分布的有用的附加信息

似乎这种类型的操作对于任何类型的分布式查询+排序实现都是常见的。例如,假设针对分布式系统的“select state,count(*)group by state”类型的查询问题(以计算每个状态中的记录数)——很自然地,您会从每个节点返回一个(state,count)对象的排序列表,然后您希望在REDUCT操作期间合并并重新排序这些对象。放弃分布式节点上已经完成的所有工作似乎很愚蠢

定量票据 我感兴趣的是要合并和重新排序的列表很小的情况:通常大约256个条目。分数的范围各不相同,在某些情况下从0到100,在其他情况下高达0到10000000。当然,考虑到元素的数量很小,每个操作在绝对时间上都会很快,即使是使用简单的算法,但执行次数总计达数十亿次

事实上,下面的一个答案是,通常情况下,对于增加列表大小(即,取n作为组合列表大小),您无法比普通排序做得更好,但事实上,对于固定大小的列表,我更感兴趣的是多次这样做,并且具有良好的经验性能

  • 维护一张地图,该地图是真实学生信息的独特映射

    Map<String, Student> scores = new HashMap<>();
    
  • 使用Java8流对入口集进行排序

    scores.entrySet()
      .stream()
      .sorted((s1, s2) -> (s2.getValue().score - s1.getValue().score)
      .map(s1 -> s1.getValue())
      .collect(Collectos.toList());
    
  • 这仍然是
    O(N Log N)


    不能使用标准合并算法对其进行排序,因为列表包含位置不相同的名称。标准合并算法不会处理同一元素两次。找到重复项并添加学生分数后,需要重新排序。您正在打破合并排序的先决条件,即两个列表始终按其值排序

    听起来你需要使用一种算法

    “如果排序算法利用其输入中的现有顺序,则属于自适应排序系列。它得益于输入序列中的预分类——或者对于各种无序度的定义来说,数量有限的无序度——并且分类速度更快。自适应排序通常是通过修改现有的排序算法来实现的

    示例包括插入排序和Timsort;有关更多信息,请参阅上面的文章。请注意,在Java8中,
    Arrays.sort(Object[])
    library方法使用修改后的Timsort


    我不知道有任何已发布的算法处理您的示例的特定要求,但这里有一个想法:

  • 对两个输入列表L1和L2执行经典合并:

    • 当合并一对对象并更改决定顺序的键时,请将合并对象放入临时列表a中
    • 否则,将对象放入临时列表B…中,该列表将保持有序
  • 对临时列表A进行排序

  • 合并列表A和B

  • 假设:

    • 原始列表L1和L2的长度分别为M和N,以及
    • 已更改关键帧的合并对象数为R(小于max(M,N))
    那么总体复杂度是O(M+N+RlogR)。如果R相对于M+N来说很小,那么这应该是一个改进


    在您的示例中,输入列表中元素之间存在匹配的每种情况都可能会按顺序移动元素。如果移动元素,它将按顺序移动到后面(而不是更早)。因此,另一个想法是在原始2个列表和优先级队列之间进行三方合并。当获得匹配时,合并计数并将结果添加到优先级队列

    复杂性与前一个类似,
      Let's use two intermediate structures:
      - a TreeSet R, which guarantees ordering by rank, 
      - an HashMap M, which guarantees constant time insertion and retrieve 
      Call R's size n
    
      1 for each student in each list
          1.1 find the student in M by name (O(1)).
          1.2 if the student is found          
             1.2.1 find the student in R by its rank (O(log(n)).  
             1.2.2 remove the student from R (O(log(n))
             1.2.3 update the student rank 
          1.3 else 
            1.3.1. put the student in M O(1)
          1.4 put the student in R (O(log(n))
      2 At the end (if needed) transform the TreeSet in a list
    
    idx   x     Dec    Inc      
    ----------------------
     1 |  1  =  1   +  0
     2 |  3  =  0   +  3
     3 |  2  =  -2  +  4
     4 | -10 =  -15 +  5
     5 |  5  =  -16 +  21
     6 |  4  =  -18 +  22
     7 |  7  =  -19 +  23
     8 |  25 =  -20 +  45
    
    A = {(1, 1), (2, 0), (3, -2), (4, -15), (5, -16), (6, -18), (7, -19), (8, -20)}
    B = {(8, 45), (7, 23), (6, 22), (5, 21), (4, 5), (3, 4), (2, 3), (1, 0)}
    
    C = {(8, 25), (7, 7), (5, 5), (6, 4), (2, 3), (3, 2), (1, 1), (4, -10)
    
    A = [(ka_i, va_i) | i = 1..n]
    B = [(kb_i, vb_i) | i = 1..m] 
    
    C = [(ka_i, va_i + va_j) | ka_i = kb_j]
    
    # (Assume 1-indexed lists)
    1. Initialize Inc = [x_1] and Dec = [0]
    2. For i = 2..n:
        a. if x[i] > x[i-1] then
              Dec.append(Dec[i-1] - 1)
              Inc.append(x_i - Dec[i])
           else   # We must have x[i] <= x[i-1]
              Inc.append(Inc[i-1] + 1)
              Dec.append(x_i - Inc[i])
    
    3. Create list A and B:
        A = [(i, Dec[i]) | i = 1..n]
        B = [(i, Inc[i]) | i = 1..n]
    4. B = reverse(B) # Reverse B because B was in increasing order and we
                      # need both lists to be in decreasing order
    5. A and B are inputs to your algorithm.
      If your algorithm can combine A and B into sorted order,
      then we have also sorted S (via argsort on the keys).
    
    public class Student {
    
            String name = "";
            int score = 0;
    
            public Student(String name, int score) {
                this.name = name;
                this.score = score;
            }
    
            @Override
            public boolean equals(Object v) {
                if (v instanceof Student) {
                    return this.name.equals(((Student) v).name);
                } else if (v instanceof String) {
                    return this.name.equals(String.valueOf(v));
                } else {
                    return false;
                }
            }
    
            @Override
            public int hashCode() {
                int hash = 7;
                hash = 67 * hash + Objects.hashCode(this.name);
                return hash;
            }
        }
    
    public class CustomComparator implements Comparator<Object> {
    
            public int orderby = 0;
    
            @Override
            public int compare(Object o1, Object o2) {
                Student st1 = (Student)o1;
                Student st2 = (Student)o2;
                if (orderby==0){
                    //order by name.
                    return st1.name.compareTo(st2.name);
                }else{
                    //order by score.
                    Integer a=st1.score;
                    Integer b = st2.score;
                    return a.compareTo(b);
                }
    
            }
        }
    
    List<Student> A = new ArrayList<Student>();
    A.add(new Student("bob", 20));
    A.add(new Student("john", 15));
    A.add(new Student("mark", 14));
    
    List<Student> B = new ArrayList<Student>();
    B.add(new Student("bill", 11));
    B.add(new Student("mark", 9));
    B.add(new Student("john", 1));
    
    List<Student> merge = new ArrayList<Student>();
    merge.addAll(A);
    merge.addAll(B);
    
    //Copy.
    List<Student> result = new ArrayList<Student>();
    for (Student st : merge) {
        if (result.contains(st)) {
            for (Student r : result) {
                if (r.equals(st)) {
                    System.out.println(st.score + " > " +r.score);
                    //Se the best score
                    if (st.score > r.score) {
                        r.score = st.score;
                        break;
                    }
                }
            }
        } else {
            result.add(st);
        }
    }
    
    //Sort result by name.
    CustomComparator comparator = new CustomComparator();
    comparator.orderby=0; //1 sort by score.
    Collections.sort(result, comparator);
    for (Student r : result) {
        System.out.println(r.name + " = " + r.score);
    }