对“已排序”数组进行排序

时间:2009-06-12 09:35:35

标签: java algorithm arrays sorting

  1. 假设给定一个大小为n的数组,并带有排序值。
  2. 在迭代i中,给出了一个新的随机生成的值,并将其插入到数组的末尾。
  3. 然后使用数组,并丢弃最小值项。
  4. 迭代n后,保留的数组将包含最大值项。
  5. 例如,在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);
    

    但似乎效率低下。有更好的算法吗?

16 个答案:

答案 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 classPriorityQueue<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)

Collections.binarySearch()

ArrayList.ensureCapcity()

您的伪代码将一组新项目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.binarySearchSystem.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)

ShellSortNatural 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]