固定大小的集合,用于保存Java中的顶级(N)值

时间:2014-08-03 19:42:35

标签: java

我需要在尝试从大整数列表(大约一百万个大小的惰性列表)中添加值时保持前N(<1000)整数。我想尝试将值添加到集合中,但是只需要保留前N个(最高值)整数。是否有任何首选数据结构可用于此目的?

7 个答案:

答案 0 :(得分:8)

我建议使用一些排序数据结构,例如TreeSet。在插入之前,检查集合中的项目数量,如果它达到1000,请删除最小的数字(如果它小于新添加的数字),并添加新的数字。

TreeSet<Integer> set = ...;

public void add (int n) {
    if (set.size () < 1000) {
       set.add (n);
    } else {
       Integer first = set.first();
       if (first.intValue() < n) {
          set.pollFirst();
          set.add (n);
       }
    }
}

答案 1 :(得分:8)

Google Guava MinMaxPriorityQueue 类。

您还可以使用比较器(使用orderedBy(Comparator<B> comparator)方法)来使用自定义排序。

注意:此集合不是已排序的集合。

请参阅javadoc

示例:

@Test
public void test() {
    final int maxSize = 5;

    // Natural order
    final MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
            .maximumSize(maxSize).create();
    queue.addAll(Arrays.asList(10, 30, 60, 70, 20, 80, 90, 50, 100, 40));

    assertEquals(maxSize, queue.size());
    assertEquals(new Integer(50), Collections.max(queue));

    System.out.println(queue);
}

<强>输出:

[10,50,40,30,20]

答案 2 :(得分:1)

一个有效的解决方案是使用二进制最小堆稍微调整基于数组的优先级队列。

前N个整数只是逐个添加到堆中,或者你可以从前N个整数的数组中构建它(稍微快一些)。

之后,将传入的整数与根元素(到目前为止找到的 MIN 值)进行比较。如果新整数大于该整数,则只需用这个新整数替换root并执行下堆操作(即,将新整数涓流,直到它的子节点都变小或成为叶子)。数据结构保证到目前为止总是有N个最大整数,平均加法时间为O(log N)。

这是我的C#实现,提到的方法名为&#34; EnqueueDown&#34;。 &#34; EnqueueUp&#34;是一个标准排队操作,它扩展了数组,添加了新的叶子并将其涓涓细流。

我已经在最大堆大小为1000的1M数字上测试了它,它运行在200毫秒以下:

namespace ImagingShop.Research.FastPriorityQueue
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;

    public sealed class FastPriorityQueue<T> : IEnumerable<Tuple<T, float>>
    {
        private readonly int capacity;
        private readonly Tuple<T, float>[] nodes;

        private int count = 0;

        public FastPriorityQueue(int capacity)
        {
            this.capacity = capacity;
            this.nodes = new Tuple<T, float>[capacity];
        }

        public int Capacity => this.capacity;

        public int Count => this.count;

        public T FirstNode => this.nodes[0].Item1;

        public float FirstPriority => this.nodes[0].Item2;

        public void Clear()
        {
            this.count = 0;
        }

        public bool Contains(T node) => this.nodes.Any(tuple => Equals(tuple.Item1, node));

        public T Dequeue()
        {
            T nodeHead = this.nodes[0].Item1;

            int index = (this.count - 1);

            this.nodes[0] = this.nodes[index];
            this.count--;

            DownHeap(index);

            return nodeHead;
        }

        public void EnqueueDown(T node, float priority)
        {
            if (this.count == this.capacity)
            {
                if (priority < this.nodes[0].Item2)
                {
                    return;
                }

                this.nodes[0] = Tuple.Create(node, priority);

                DownHeap(0);

                return;
            }

            int index = this.count;

            this.count++;

            this.nodes[index] = Tuple.Create(node, priority);

            UpHeap(index);
        }

        public void EnqueueUp(T node, float priority)
        {
            int index = this.count;

            this.count++;

            this.nodes[index] = Tuple.Create(node, priority);

            UpHeap(index);
        }

        public IEnumerator<Tuple<T, float>> GetEnumerator()
        {
            for (int i = 0; i < this.count; i++) yield return this.nodes[i];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void DownHeap(int index)
        {
            while (true)
            {
                int indexLeft = (index << 1);
                int indexRight = (indexLeft | 1);

                int indexMin = ((indexLeft < this.count) && (this.nodes[indexLeft].Item2 < this.nodes[index].Item2))
                    ? indexLeft
                    : index;

                if ((indexRight < this.count) && (this.nodes[indexRight].Item2 < this.nodes[indexMin].Item2))
                {
                    indexMin = indexRight;
                }

                if (indexMin == index)
                {
                    break;
                }

                Flip(index, indexMin);

                index = indexMin;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Flip(int indexA, int indexB)
        {
            var temp = this.nodes[indexA];
            this.nodes[indexA] = this.nodes[indexB];
            this.nodes[indexB] = temp;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void UpHeap(int index)
        {
            while (true)
            {
                if (index == 0)
                {
                    break;
                }

                int indexParent = (index >> 1);

                if (this.nodes[indexParent].Item2 <= this.nodes[index].Item2)
                {
                    break;
                }

                Flip(index, indexParent);

                index = indexParent;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

基本实现取自&#34; Cormen,Thomas H.算法简介。麻省理工学院出版社,2009年。&#34;

答案 3 :(得分:1)

在Java 1.7中,可以使用java.util.PriorityQueue。要保留前N项,您需要使用反向比较器,例如对于整数,你命令它们降序。通过这种方式,最小的数字始终位于顶部,如果队列中有许多项目,则可以删除。

package eu.pawelsz.example.topn;

import java.util.Comparator;
import java.util.PriorityQueue;

public class TopN {

  public static <E> void add(int keep, PriorityQueue<E> priorityQueue, E element) {
    if (keep == priorityQueue.size()) {
      priorityQueue.poll();
    }
    priorityQueue.add(element);
  }

  public static void main(String[] args) {
    int N = 4;
    PriorityQueue<Integer> topN = new PriorityQueue<>(N, new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {
        return o1 - o2;
      }
    });

    add(N, topN, 1);
    add(N, topN, 2);
    add(N, topN, 3);
    add(N, topN, 4);
    System.out.println("smallest: " + topN.peek());
    add(N, topN, 8);
    System.out.println("smallest: " + topN.peek());
    add(N, topN, 5);
    System.out.println("smallest: " + topN.peek());
    add(N, topN, 2);
    System.out.println("smallest: " + topN.peek());
  }
}

答案 4 :(得分:1)

// this Keep Top Most K Instance in Queue 

public static <E> void add(int keep, PriorityQueue<E> priorityQueue, E element) {
    if(priorityQueue.size()<keep){
       priorityQueue.add(element);
    }
    else if(keep == priorityQueue.size()) {
      priorityQueue.add(element);    // size = keep +1 but
      Object o = (Object)topN.toArray()[k-1];
      topN.remove(o);                // resized to keep         
    }
}

答案 5 :(得分:0)

最快的方法可能是一个简单的数组items = new Item[N];和一个旋转游标int cursor = 0;。光标指向下一个元素的插入点。

要添加新元素,请使用方法

put(Item newItem) { items[cursor++] = newItem; if(cursor == N) cursor = 0; }

访问此结构时,您可以通过重新计算索引来使最后一项添加到索引0处,即

get(int index) { return items[ cursor > index ? cursor-index-1 : cursor-index-1+N ]; }

(-1是因为光标始终指向下一个插入点,即光标-1是添加的最后一个元素。)

总结:put(item)将添加一个新项目。 get(0)将获得最后一项添加,get(1)将获得最后一项,等等。

如果你需要照顾n&lt;添加了N个元素,您只需要检查null。

(TreeSet可能会更慢)

答案 6 :(得分:-2)

你的问题在这里得到解答: Size-limited queue that holds last N elements in Java

总结一下: 没有默认的java sdk中没有数据结构,但Apache commons集合4有一个CircularFifoQueue。