例如,在Java语法中,它将类似于:
List l = new ArrayList();
l.add(new Integer(2));
l.add(new Integer(3));
l.add(new Integer(6));
l.add(new Integer(9));
Random rand = new Random();
for (int i=0; i < n; i++) {
l.add(new Integer(rand.nextInt(1000)));
}
Collections.sort(l);
l.remove(0);
但似乎效率低下。有更好的算法吗?
答案 0 :(得分:13)
使用二进制插入(类似于二进制搜索)作为新值。丢弃最小的。应该很快。
顺便说一下 - 这可以作为一个方便的扩展方法实现:
private static int GetSortedIndex( this IList list, IComparer comparer, object item, int startIndex, int endIndex )
{
if( startIndex > endIndex )
{
return startIndex;
}
var midIndex = startIndex + ( endIndex - startIndex ) / 2;
return comparer.Compare( list[midIndex], item ) < 0 ?
GetSortedIndex( list, comparer, item, midIndex + 1, endIndex ) :
GetSortedIndex( list, comparer, item, startIndex, midIndex - 1 );
}
public static void InsertSorted( this IList list, IComparer comparer, object item )
{
list.Insert( list.GetSortedIndex( comparer, item ), item );
}
Java等效
public static void main(String[] args)
{
List l = new ArrayList();
l.add(new Integer(2));
l.add(new Integer(3));
l.add(new Integer(6));
l.add(new Integer(9));
Random rand = new Random();
for (int i=0; i < 10; i++) {
Integer rnd = new Integer(rand.nextInt(1000));
int pos = Collections.binarySearch(l,rnd);
if(pos < 0) pos = ~pos;
l.add(pos,rnd);
}
System.out.println(l);
}
答案 1 :(得分:8)
使用TreeSet代替List
,它会保持顺序,使得最大值始终为SortedSet#last()。如果使用1.6+,您可以使用NavigableSet方法; pollLast()将返回并删除最高值。
NavigableSet<Integer> set = new TreeSet<Integer>();
//... setup data
Integer highest = set.pollLast();
set.add(rand.nextInt(1000));
Integer newHighest = set.pollLast();
答案 2 :(得分:5)
使用min-heap存储数据,每次插入新的随机值后,在O(1)时间内删除min。
在n次迭代之后,执行n extract-min以获取排序列表。
答案 3 :(得分:5)
我很惊讶没人提到这个...你正在寻找的数据结构是priority queue。毫无疑问,这是完成此任务的最有效方式。可以使用许多不同的方法实现优先级队列(请参阅链接的文章),但最常见的是基于binary heap。在自我二元变体(非常典型)中,插入和删除都需要O(log n)
时间。
Java库中似乎有一个built-in generic class,PriorityQueue<E>
,所以看起来你可以直接使用它。这种类型似乎并不令人惊讶地基于堆数据结构,尽管比我不能说的更具体。无论如何,它应该非常适合您的使用。
答案 4 :(得分:3)
一个非常简单的优化是在排序之前将排序数组中的最低值(应该是第一项)与新值进行比较。如果新值大于此值,请使用新值替换该元素,然后使用该数组。
答案 5 :(得分:2)
我能想到的最快的算法是用新的算法替换最小的元素(如果需要的话),并通过重复交换相邻的元素将新的元素推到适当的位置。
编辑:代码假定数组按降序排序,因此最后一个元素是最小的。
void Insert(int[] array, int newValue)
{
// If the new value is less than the current smallest, it should be
// discarded
if (new_value <= array[array.length-1])
return;
array[array.length-1] = newValue;
for (int i = array.length-1; i > 0; --i)
{
if (newValue <= array[i-1])
break;
// Swap array[i] with array[i-1]
array[i] = array[i-1];
array[i-1] = newValue;
}
}
答案 6 :(得分:2)
您的伪代码将一组新项目N插入到大小为S的排序列表A中,然后丢弃最小的项目。使用 Collections.binarySearch()查找插入点。 [如果您的列表不支持RandomAccess,请阅读说明性能影响。 ArrayList支持RandomAccess。]
List<Integer> l = new ArrayList<Integer>();
l.add(new Integer(2));
l.add(new Integer(3));
l.add(new Integer(6));
l.add(new Integer(9));
l.ensureCapacity(l.size()+n);
Random rand = new Random();
for (int i=0; i < n; i++) {
final Integer newInt = Integer.rand.nextInt(1000);
int insertPoint = Collections.binarySearch(l, newInt);
if (insertPoint < 0) insertPoint = -(insertPoint + 1);
l.add(insertPoint, newInt);
}
l.remove(0);
但是,你确定要丢弃一件物品吗?或者您的意思是将一组新项目N插入到大小为S的排序列表A中并仅保留S个最大项目。在这种情况下,请跟踪最小值:
int min = l.get(0);
l.ensureCapacity(l.size()+n);
Random rand = new Random();
for (int i=0; i < n; i++) {
final Integer newInt = Integer.rand.nextInt(1000);
if (newInt > min) {
int insertPoint = Collections.binarySearch(l, newInt);
if (insertPoint < 0) insertPoint = -(insertPoint + 1);
l.add(insertPoint, newInt);
}
}
但是,如果N很大,你可能最好自己将N排序到一个排序数组中,丢弃较小的N(0)或A(0),然后将两个排序的数组合并在一起[left as a为读者锻炼]。
如果您最终使用的是实际数组,请参阅Arrays.binarySearch和System.arraycopy。
答案 7 :(得分:1)
您可以使用二进制搜索将值插入已排序的数组中。
答案 8 :(得分:1)
如果您正在使用ArrayList,则在排序数组之前,如果新数字较大,则可以使用新数字替换数组中的最后一个数字。
Java Collections.sort
使用合并排序,这在这种情况下不是最有效的排序方式。您希望使用二进制搜索来查找插入点,然后将所有后续数字一起移动。
编辑:这一切都可以通过这样的数组完成:
public static int addDiscard(int[] list, int number)
{
if (number > list[list.length - 1])
{
int index = findInsertionIndex(list, number); // use binary search
for (int i = list.length - 1; i > index; i--)
{
list[i] = list[i - 1];
}
list[index] = number;
}
}
答案 9 :(得分:1)
我不知道您是否可以更改数据结构,或者您需要支持哪些其他操作,但堆更适合您描述的操作类型。
答案 10 :(得分:1)
这将使大小保持在4并按照我的理解做你想做的事。
SortedSet<Integer> set = new TreeSet<Integer>();
set.add(2);
set.add(3);
set.add(6);
set.add(9);
Random rand = new Random();
for (int i=0; i < n; i++) {
int i = rand.nextInt(1000);
set.remove(set.first());
set.add(i);
}
答案 11 :(得分:0)
ShellSort
和Natural Mergesort
在很大程度上预先排序的数据上非常高效(&lt; O(n logn))。
插入带有binary search
的排序列表需要更多时间,因为无论如何一次更新都需要O(n)。
或者,您可以使用堆数据结构。
答案 12 :(得分:0)
你真的需要一次在线的一项算法吗?或者你实际上正在解析更大的数据集合,只想要前n项?如果是后者,请查看partial qsort。
答案 13 :(得分:0)
我不确定上面的例子是否有效,n是什么?如果你循环添加从1到1000的随机#,你总会得到1000,999,998和997 - 不是吗?我不认为添加#然后每次使用都是有效的 - 检查四个位置中的每一个并用更高的#替换可能会更快。
很大程度上取决于你将添加多少个随机数#,少数#添加并检查4个位置中的每个位置,很多#添加只是假设你获得该范围内的最高值。
答案 14 :(得分:0)
一个关键问题是,您是否需要知道每个新项目生成后的前4项,或者您是否只需要在生成所有项目后需要前4项。此外,它是4个顶级项目,还是仅仅是一个示例或插图?
因为如果你真的在生成数千个值并且只想要前4个,我会认为将每个新值与现有4个值进行比较并丢弃,如果少于全部值将比执行许多值快得多排序。对于每个新项目,这只是4个比较,而不是重复排序的可能更大的数字。
同样地,如果你只需要在流程结束时使用前N个,那么将它们全部收集,排序,然后取得前N个可能会更快。但是,如果大多数值被消除,那么排序“失败者”的相对位置可能是一个很大的浪费时间。如果我们只想要前4名,那么项目是#5还是#10,382,842是无关紧要的。
答案 15 :(得分:0)
这是另一种解决方案,它将操作合并为搜索,数组副本和值集。这样就不需要排序或循环了。
public static <T extends Comparable<T>>
void insertAndRemoveSmallest(T[] array, T t) {
int pos = Arrays.binarySearch(array, t);
if (pos < 0) pos = ~pos;
// this is the smallest entry so no need to add it and remove it.
if (pos == 0) return;
pos--;
// move all the entries down one.
if (pos > 0) System.arraycopy(array, 1, array, 0, pos);
array[pos] = t;
}
这个程序
public static void main(String... args) {
Integer[] ints = {2, 3, 7, 6, 9};
System.out.println("Starting with " + Arrays.toString(ints));
for (int i : new int[]{5, 1, 10, 8, 8}) {
insertAndRemoveSmallest(ints, i);
System.out.println("After adding " + i + ": " + Arrays.toString(ints));
}
}
打印
Starting with [2, 3, 7, 6, 9]
After adding 5: [3, 5, 7, 6, 9]
After adding 1: [3, 5, 7, 6, 9]
After adding 10: [5, 7, 6, 9, 10]
After adding 8: [7, 6, 8, 9, 10]
After adding 8: [6, 8, 8, 9, 10]