【问题标题】:Whats the most efficient way to sort elements of an array based on frequency of elements什么是根据元素频率对数组元素进行排序的最有效方法
【发布时间】:2020-05-06 10:39:05
【问题描述】:

我有一个数组,我使用以下方法根据元素的频率对数组进行排序。

  1. 我将元素及其各自的频率存储为键值对。
  2. 我将值(元素的频率)存储在一个数组中
  3. 我按降序对数组进行排序
  4. 我打印与值对应的键
public void sort(int a[])
    {
        HashMap<Integer, Integer> hs = new HashMap<>();

        for(int i = 0;i<a.length;i++)
        {
            Integer j = new Integer(a[i]);
            if(hs.containsKey(j))
                hs.put(j,hs.get(j)+1);
            else
                hs.put(j,1);
        }
        Integer g[] = new Integer[hs.size()];
        int v= 0;
        for(Map.Entry<Integer,Integer> entry : hs.entrySet())
            g[v++] = entry.getValue();

        for(int i = 0;i<v-1;i++)
            {
                for(int j=i+1;j<v;j++)
                {
                    if(g[j]>g[i])
                    {
                        int temp = g[j];
                        g[j] = g[i];
                        g[i] = temp;
                    }
                }
            }
   int i=0;
    while(i != v){
    for(Map.Entry<Integer,Integer> entry : hs.entrySet())
    {
            if(g[i] == entry.getValue())
            System.out.println(entry.getKey() +" = "+ g[i++]);
    }
    }


    }

请问有什么简单有效的方法吗?

【问题讨论】:

  • 允许使用 java 8 及以上版本??
  • Arrays.stream(yourArray).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
  • @Michael , .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) 最终会出现在 Map&lt;Integer, Long&gt; ,但 OP 需要 Map&lt;Integer, Integer&gt; 所以在这里使用 Collectors.reducing(0, e -&gt; 1, Integer::sum) 是最好的。
  • @VishwaRatna OP 并不“需要”Map&lt;Integer, Integer&gt; - 目标是按指定顺序对数组进行排序,而不是创建特定类型的映射。 map 是一种辅助数据结构,如果这样可以简化代码,可以轻松更改为 Map&lt;Integer, Long&gt; 类型。

标签: java arrays algorithm sorting hashmap


【解决方案1】:

这是另一个解决方案:

public static void sort(int[] a) {
    Map<Integer, Integer> hs = new HashMap<>();
    for (int i = 0; i < a.length; i++) {
        Integer j = a[i];
        if (hs.containsKey(j)) {
            hs.put(j, hs.get(j) + 1);
        } else {
            hs.put(j, 1);
        }
    }
    List<Map.Entry<Integer,Integer>> list = new ArrayList<>(hs.entrySet());
    Collections.sort(list, (x,y)->{
        int r = x.getValue().compareTo(y.getValue());
        if (r == 0) {
            r = x.getKey().compareTo(y.getKey());
        }
        return r;
    });
    for (Map.Entry<Integer, Integer> entry : list) {
        System.out.println(entry.getKey() + ": " + entry.getValue());
    }
}

【讨论】:

    【解决方案2】:

    不知道short,但您可以实现一个简单的priority queue,这使您的代码更具可读性。时间复杂度为O(n log(n))

    在优先队列中,我们只会将频率较高的元素放在顶部。

    片段:

    import java.util.*;
    public class Main{
        static class Element{
            int value,freq;
            Element(int value,int freq){
                this.value = value;
                this.freq = freq;
            }
        }
        public static void main(String[] args) {
            sort(new int[]{2,2,4,5,7,6,4,8,3,9,5,7,2});
        }
    
        public static void sort(int a[]){
            Map<Integer,Integer> map = new HashMap<>();
            for(int i=0;i<a.length;++i){
                map.merge(a[i], 1, Integer::sum);
            }
    
            PriorityQueue<Element> pq = new PriorityQueue<Element>(1000,(c,d) -> (d.freq - c.freq));
            for(Map.Entry<Integer,Integer> m : map.entrySet()){
                pq.offer(new Element(m.getKey(),m.getValue()));
            }
    
            while(!pq.isEmpty()){
                Element e = pq.poll();
                System.out.println(e.value + " => " + e.freq);
            }
        }
    }
    

    演示: https://onlinegdb.com/HJpdDX7bI

    【讨论】:

    • 您可以在第一个 for 循环中使用 map.merge(a[i], 1, Integer::sum) 代替这两条指令。
    【解决方案3】:

    从 Java-8 开始:

      int [] array = {2,2,4,5,7,6,4,8,3,9,5,7,2};
      Map<Integer, Integer> collect = Arrays.stream(array).boxed()
          .collect(Collectors.groupingBy(Function.identity(), Collectors.reducing(0, e -> 1, Integer::sum)));
      System.out.println(collect);
    

    请注意,如果您使用 Collectors.counting(),那么您最终会得到 Map&lt;Integer, Long&gt;,但根据您的解决方案,您似乎想要 Map&lt;Integer, Integer&gt;,因此请使用 Collectors.reducing(0, e -&gt; 1, Integer::sum)

    【讨论】:

    • 最初的问题不清楚“高效”是什么意思。如果这意味着:在简单、简短的代码中——这肯定会赢得大奖;)——但这种算法不一定特别节省内存,因为它必须进入盒装领域。
    【解决方案4】:

    具有比您的示例小得多的额外内存负载(在最坏的情况下仅增加 200%),以及 O(n log n) 的性能特征;我很确定您无法在算法上击败它,尽管通过大量额外的工作,您可以将内存负载降低到最坏情况下的 100% 额外;可能不值得做。

    算法,简而言之:

    创建一个新的 long 数组。每个 long 都被打包;它包含 2 条单独的信息:低 32 位是正常的数字(这适合,因为整数是 32 位);高 32 位用于存储频率(数字在输入中出现的频率)。

    为了有效地制作新的长数组,首先对输入数组进行排序;这需要 O(n log n),这意味着您可以在输入数组中循环一次以创建长数组。然后,对那个进行排序,这会将最频繁的元素放在后面。如果需要,如果你想在前面最频繁地反转它。 (较高频率的 bitpacked 数字较大,因为其较高的 32 位最高);这是另一个 O(n log n) 操作。所以,它是 2 个 O(n log n) 操作和一个 O(n) 操作,总共是 O(n log n)。

    API 和 java 知识的相关知识:

    • 排序 int 和 long 数组:java.util.Arrays.sort(int[] or long[])
    • bitpacking:您需要 | 运算符和 &lt;&lt; 运算符。将 2 个整数位打包成一个长整数,例如:

      int 计数 = 5; // 表示“数字”显示的频率。 整数 = 85; 长包装=(((长)计数)

    • bit unpacking: 将打包的 long 变回组成部分:

      长包装 = ....; // 根据上面的计算 int count = (int) (packed >> 32); // 将是 5 int number = (int) 打包; // 将是 85

    你需要做一些花哨的步骤来处理包含打包信息的 long[] 数组;毕竟,这个数组的元素可能更少(给定输入 [1,2,5,1,2],您的打包长数组有 3 个条目:[{count=2, number=1}, {count =2, number=2} 和 {count=1, number=5}],所以只有 3 个条目;要么首先检查你的排序输入数组一次以确定它需要多大,然后再做一次来填充它,或使长数组与输入数组一样大(在最坏的情况下,它必须有多大,即输入数组中的所有数字都是唯一的),然后编写代码以知道您的输入数组中的 0长数组将被忽略。

    要填充长数组,只需遍历已排序的输入数组。它看起来像 [1,1,2,2,5],您需要将其转换为 [packed(2,1),packed(2,2),packed(1,5)]。我会把它留给你做练习。

    【讨论】:

      【解决方案5】:

      您可以从地图的 entrySet 创建一个列表,然后使用自定义比较器对列表进行排序,该比较器根据地图的值进行比较。

      1. 从 Map 的 entrySet 创建一个列表 - 使用将另一个集合作为参数的构造函数。
      2. 对列表进行排序 - Collections.sort 使用 Comparator 实现。

      【讨论】:

        猜你喜欢
        • 2019-06-10
        • 1970-01-01
        • 2020-08-26
        • 2015-10-18
        • 2020-10-19
        • 2021-12-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多