根据Java中的元素频率对数组元素进行排序

时间:2018-08-20 06:36:49

标签: java algorithm sorting

我已经编写了一个代码,可以根据Java中数组元素的频率对数组进行排序。我需要更好的代码或伪代码(没有收集框架)。请提供链接或代码帮助。

public class SortByFreq1 {

    public static void main(String[] args) {

        int arr[] = { 2, 5, 2, 8, 5, 6, 8, 8, 0, -8 };

        int nArr[] = new int[arr.length];

        Map<Integer,Integer> map = new HashMap<Integer, Integer>();
        Map<Integer,Integer> sortmap = new HashMap<Integer, Integer>();
        ArrayList<Integer> arrList = new ArrayList<Integer>();

        for (int i = 0; i < arr.length; i++) {
            arrList.add(arr[i]);
        }

        Set<Integer> set = new HashSet<Integer>(arrList);

        for (Integer i : set) {  
            map.put(i, Collections.frequency(arrList, i));   
        }

        // System.out.println(map.keySet());
        // sort map by value

        Set<Entry<Integer,Integer>> valList=map.entrySet();
        ArrayList<Entry<Integer, Integer>> tempLst = new ArrayList<Map.Entry<Integer, Integer>>(valList);

        Collections.sort(tempLst, new Comparator<Entry<Integer, Integer>>() {
            @Override
            public int compare(Entry<Integer, Integer> o1, Entry<Integer, Integer> o2) {
                return o2.getValue().compareTo(o1.getValue());
            }
        });

        int k = 0;

        for (Entry<Integer, Integer> entry : tempLst) {
            int no = entry.getKey();
            int noOfTimes = entry.getValue();

            int i = 0;

            while (i < noOfTimes) {
                nArr[k++] = no;
               i++;
            }
        }

        for (int i = 0; i < nArr.length; i++)
            System.out.print(nArr[i] + " ");
    }
} 

4 个答案:

答案 0 :(得分:1)

其背后的逻辑与Counting Sort十分相似。

注意:我们来修改传入的数组。

有两种不同的方法,它们的时间和空间复杂度几乎相同。

  • 时间复杂度:max(n,O(klogk));
  • 空间复杂度:O(n)-要返回的数组;
  上面提到的

k是数组中不同数字的数量。

内置收集方法

使用Stream也许可以使过程更加简洁,尽管OP并没有要求这样做:

/**
 * 1. count the frequency and sort the entry based on the frequency while using LinkedHashMap to retain the order;
 * 2. fill up the new array based on the frequency while traversing the LinkedHashMap;
 * @param arr
 * @return
 */
private static int[] sortByCounting(int[] arr) {
    Map<Integer, Long> countMap = Arrays.stream(arr).boxed()
            .collect(Collectors.groupingBy(Integer::intValue, Collectors.counting()))
            .entrySet().stream()
            .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldV, newV) -> oldV, LinkedHashMap::new));
    int[] newArr = new int[arr.length];
    int i = 0;
    for (Map.Entry<Integer, Long> entry : countMap.entrySet()) {
        Arrays.fill(newArr, i, i += entry.getValue().intValue(), entry.getKey());
    }
    return newArr;
}

自定义方法

由于我们无法使用内置收集方法,因此我们必须记录该数字的计数。

本能地,我们可以引入一个自定义对,以将number及其相关的frequency(或我们可以说的count)记录为我们的自定义方法


private static int[] sortByPlainCounting(int[] arr) {
    if (arr.length < 1) throw new IllegalArgumentException("Array cannot be empty");
    MyPair[] pairs = prepareMyPairs(arr);
    Arrays.sort(pairs, Comparator.comparing(MyPair::getCount).reversed());
    int[] newArr = new int[arr.length];
    int i = 0;
    for (MyPair pair : pairs) {
        Arrays.fill(newArr, i, i += pair.count, pair.key);
    }
    return newArr;
}

static class MyPair {
    int key;
    int count;

    public MyPair(int theKey) {
        this.key = theKey;
        this.count = 1;
    }

    public void inc() {
        this.count++;
    }

    public int getCount() {
        return this.count;
    }
}

static MyPair[] prepareMyPairs(int[] arr) {
    Integer[] tmpArr = Arrays.stream(arr).boxed().toArray(Integer[]::new);
    Arrays.sort(tmpArr, Comparator.reverseOrder());
    int count = 1;
    int prev = tmpArr[0];
    for (int i = 1; i < tmpArr.length; i++) {
        if (tmpArr[i] != prev) {
            prev = tmpArr[i];
            count++;
        }
    }
    MyPair[] pairs = new MyPair[count];
    int k = 0;
    for (int i = 0; i < tmpArr.length; i++) {
        if (pairs[k] == null) {
            pairs[k] = new MyPair(tmpArr[i]);
        } else {
            if (pairs[k].key == tmpArr[i]) {
                pairs[k].inc();
            } else {
                k++; i--;
            }
        }
    }
    return pairs;
}

比较和演示

进行最终比较,我们可以证明:

  1. 自定义的平均时间成本稍微差一点(差1.4倍),而最坏情况要比内置收集方法好得多(好4倍)。
  2. 自定义方法是正确的;

public static void main(String[] args) {
    int N = 10_000 + new Random().nextInt(100);
    Long start;
    List<Long> list0 = new ArrayList<>();
    List<Long> list1 = new ArrayList<>();
    for (int i = 0; i < 100; ++i) {
        int[] arr = RandomGenerator.generateArrays(N, N, N / 10, N / 5, false);

        start = System.nanoTime();
        int[] arr0 = sortByCounting(arr);
        list0.add(System.nanoTime() - start);

        start = System.nanoTime();
        int[] arr1 = sortByPlainCounting(arr);
        list1.add(System.nanoTime() - start);

        System.out.println(isFrequencyEqual(arr0, arr1));
    }
    System.out.println("Collection time cost: " + list0.stream().collect(Collectors.summarizingLong(Long::valueOf)));
    System.out.println("Custom   time   cost: " + list1.stream().collect(Collectors.summarizingLong(Long::valueOf)));
}


private static boolean isFrequencyEqual(int[] arr0, int[] arr1) {
    Map<Integer, Long> countMap0 = getCountMap(arr0);
    Map<Integer, Long> countMap1 = getCountMap(arr1);
    boolean isEqual = countMap0.entrySet().size() == countMap1.entrySet().size();
    if (!isEqual) return false;
    isEqual = countMap0.values().containsAll(countMap1.values()) &&
            countMap1.values().containsAll(countMap0.values());
    if (!isEqual) return false;
    List<Long> countList0 = countMap0.values().stream().collect(Collectors.toList());
    List<Long> countList1 = countMap1.values().stream().collect(Collectors.toList());
    for (int i = 0; i < countList0.size(); i++) {
        if (countList1.get(i) != countList0.get(i)) return false;
    }
    return true;
}

private static Map<Integer, Long> getCountMap(int[] arr) {
    return Arrays.stream(arr).boxed()
            .collect(Collectors.groupingBy(Integer::intValue, Collectors.counting()))
            .entrySet().stream()
            .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldV, newV) -> oldV, LinkedHashMap::new));
}

helper util方法:

public static int[] generateArrays(int minSize, int maxSize, int low, int high, boolean isUnique) {
    Random random = new Random(System.currentTimeMillis());
    int N = random.nextInt(maxSize - minSize + 1) + minSize;
    if (isUnique) {
        Set<Integer> intSet = new HashSet<>();
        while (intSet.size() < N) {
            intSet.add(random.nextInt(high - low) + low);
        }
        return intSet.stream().mapToInt(Integer::intValue).toArray();
    } else {
        int[] arr = new int[N];
        for (int i = 0; i < N; ++i) {
            arr[i] = random.nextInt(high - low) + low;
        }
        return arr;
    }
}

测试输出:

Sorted by frequency: true
// ... another 98 same output
Sorted by frequency: true
Collection time cost: LongSummaryStatistics{count=100, sum=273531781, min=466684, average=2735317.810000, max=131741520}
Custom   time   cost: LongSummaryStatistics{count=100, sum=366417748, min=1733417, average=3664177.480000, max=27617114}

答案 1 :(得分:1)

可以使用信鸽排序在O(n)中完成。伪代码:

counts = new HashMap<Item, int>(),;
foreach item in elements:
     counts[item] += 1;
buckets = new List<Item>[elements.length+1];
foreach item in counts:
    buckets[counts[item]].Append(item)

for i from 1 to elements.length:
   bucket = buckets[i]; /* edit: looping over i instead over bucket */
   for item in bucket:
      /* edit: if the out has to preserve the original number of elements
               such as [1,5,5,0,1,9,1] will print
               9,0,5,5,1,1,1 instead of 9,0,5,1, then the next line
               has to be repeated i times*/
      System.out.println(item)

edit:通过实现哈希表和链接列表,无需收集框架即可编写相同的内容:

class Node {
   public Node next;
   public int value;
};
log2count = Math.ceil(Math.log(elements.length) / Math.log(2));
hashSize = (int) Math.Round(Math.Pow(2, log2count) * 2);

/* countsQuadraticProbing[i] is 0 if the hash entry is empty,
   otherwise it contains the frequency of the element in
   elementsQuadraticProbing[i].
   Note that quadratic probing over a hash table of size 2**k,
   and probing of  (0.5 i + 0.5 i**2) is guaranteed to find an empty
   entry if the hash table is not full.
*/
countsQuadraticProbing = new int[hashSize];
elementsQuadraticProbing = new int[hashSize];
foreach item in elements:
     for i from 0 to hashSize-1:
        index = (item + (i * (i + 1) / 2)) % hashSize;
        if countsQuadraticProbing[index] == 0:
           countsQuadraticProbing[index] = 1;
           elementsQuadraticProbing[index] = item;
           break;
        if elementsQuadraticProbing[index] == item:
           countsQuadraticProbing[index]++;
           break;

buckets = new Node[elements.length+1];
for i from 0 to hashSize-1:
    count = countsQuadraticProbing[index];
    if count != 0:
       Node topNode = new Node();
       topNode.next = buckets[count];
       topNode.value = elementsQuadraticProbing[i];
       buckets[count] = topNode;

/* there are O(N) buckets, total of elements in all buckets O(N),
   overall complexity of the nested loop O(N)
*/
for i from 1 to elements.length:
   node = buckets[i]  /* edit: using i for iteration */
   while node != null:
      /* edit: if the out has to preserve the original number of elements
               such as [1,5,5,0,1,9,1] will print
               9,0,5,5,1,1,1 instead of 9,0,5,1, then the next line
               has to be repeated i times*/
      System.out.println(node.value);
      node = node.next;

答案 2 :(得分:0)

您的解决方案更好,但是由于不使用任何集合,因此可能会很大。

1。对列表进行排序 2.获取每个元素的频率 3.创建一个新的arraylist /数组并存储频率较高的元素到频率较低的元素。

  1. 使用任何排序算法对列表进行排序
  2. 获取频率

      class CountFrequencies 
    {
     // Function to find counts of all elements present in
     // arr[0..n-1]. The array elements must be range from
     // 1 to n
     void findCounts(int arr[], int n) 
     {
    // Traverse all array elements
    int i = 0;
    while (i < n) 
    {
        // If this element is already processed,
        // then nothing to do
        if (arr[i] <= 0) 
        {
            i++;
            continue;
        }
    
        // Find index corresponding to this element
        // For example, index for 5 is 4
        int elementIndex = arr[i] - 1;
    
        // If the elementIndex has an element that is not
        // processed yet, then first store that element
        // to arr[i] so that we don't loose anything.
        if (arr[elementIndex] > 0) 
        {
            arr[i] = arr[elementIndex];
    
            // After storing arr[elementIndex], change it
            // to store initial count of 'arr[i]'
            arr[elementIndex] = -1;
        } 
        else
        {
            // If this is NOT first occurrence of arr[i],
            // then increment its count.
            arr[elementIndex]--;
    
            // And initialize arr[i] as 0 means the element
            // 'i+1' is not seen so far
            arr[i] = 0;
            i++;
        }
    }
    
    System.out.println("Below are counts of all elements");
    for (int j = 0; j < n; j++)
        System.out.println(j+1 + "->" + Math.abs(arr[j]));
     }
    

以上代码应为您提供输出:

1 -> 3
2 -> 0
3 -> 2
4 -> 0
5 -> 2
6 -> 0
7 -> 2
8 -> 0
9 -> 2
10 -> 0
11 -> 0
  1. 现在,您可以轻松地使用存储了每个元素频率的数组来创建一个新数组,其中包含该数组中最频繁出现的元素 请注意,对列表进行了排序,使得arr [0]的频率为1 arr [1]的频率为2,依此类推。同样,该代码效率不高,因此最好使用集合框架。 或者如果您熟悉使用二叉树,则可以将元素添加到树中并使用顺序遍历! 希望您的回答对我有帮助

答案 3 :(得分:0)

我很好奇为什么您不能使用旧的冒泡排序,而只是自定义 Bubble ?在最坏的情况下,时间复杂度将为O(n * n),空间复杂度将为O(3n):)

纯数组的实现将类似于:

private static void bubbleSortByOccurrences(int[] arr) {
    int[][] counter = new int[2][arr.length];
    int counterIndex = -1;

    for (int value : arr) {
        int idx = 0;
        for (; idx <= counterIndex; idx++) {
            if (counter[0][idx] == value) {
                counter[1][idx]++;

                while (idx > 0 && counter[1][idx] > counter[1][idx-1]) {
                    int temp = counter[1][idx];
                    counter[0][idx] = counter[0][idx-1];
                    counter[1][idx] = counter[1][idx-1];
                    counter[0][idx-1] = value;
                    counter[1][idx-1] = temp;
                    idx--;
                }
                break;
            }
        }

        if (idx > counterIndex) {
            counter[0][idx] = value;
            counter[1][idx]++;
            counterIndex = idx;
        }
    }

    fillArrayBackwards(arr, counter, counterIndex);
}

private static void fillArrayBackwards(int[] buf, int[][] counter, int counterIndex) {
    for (int i = counterIndex, j = buf.length - 1; i >=0; i--) {
        for (int k = 0; k < counter[1][i]; k++) {
            buf[j--] = counter[0][i];
        }
    }
}

使用Bubble类实现的相同算法如下所示:

private static void bubbleSortByOccurrences(int[] arr) {
    Bubble head = null;

    for (int value : arr) {
        if (head == null) {
            head = new Bubble(value);
        } else {
            Bubble currentHead = null;
            Bubble current = head;

            for (; current != null && !(current.getValue() == value); current = current.getTail()) {
                currentHead = current;
            }

            if (current == null) {
                current = new Bubble(value);
                current.setTail(head);
                head = current;
            } else {
                current.incrementOccurrences();

                while (current.getTail() != null && current.getOccurrences() > current.getTail().getOccurrences()) {
                    Bubble currentTail = current.getTail();
                    current.setTail(currentTail.getTail());
                    currentTail.setTail(current);

                    if (currentHead != null) {
                        currentHead.setTail(currentTail);
                        currentHead = currentTail;
                    } else {
                        head = currentTail;
                    }
                }
            }
        }
    }

    fillArrayBackwards(arr, head);
}

private static void fillArrayBackwards(int[] buf, Bubble head) {
    int i = buf.length - 1;
    for (Bubble current = head; current != null; current = current.getTail()) {
        for (int j = 0; j < current.getOccurrences(); j++) {
            buf[i--] = current.getValue();
        }
    }
}

自定义气泡如下:

class Bubble {
    private int value;
    private int occurrences;
    private Bubble tail;

    public Bubble(int value) {
        this.value = value;
        this.occurrences = 1;
    }

    public int getValue() {
        return value;
    }

    public int getOccurrences() {
        return occurrences;
    }

    public void incrementOccurrences() {
        this.occurrences++;
    }

    public Bubble getTail() {
        return tail;
    }

    public void setTail(Bubble tail) {
        this.tail = tail;
    }
}