Java 对于100000000个元素数组,快速排序需要5个小时,这正常吗?

Java 对于100000000个元素数组,快速排序需要5个小时,这正常吗?,java,algorithm,Java,Algorithm,在Java中使用最后一个数组作为轴心来实现基本算法,对100000000个随机数元素数组进行排序需要5小时,这正常吗 我的系统规格: Mac OS X Lion 10.7.2 2011 英特尔酷睿i5 2.3 GHz 8GB内存 更新2:所以我认为我的其他方法有问题,因为Narendra能够运行快速排序。下面是我试图运行的完整代码 我已经把这个旧的,现在无关紧要的答案移到了最后 编辑x2 啊哈!我想我已经找到了你糟糕表现的原因。你告诉我们你使用的是随机数据。这是真的。但你没有告诉我们的是,你使用

在Java中使用最后一个数组作为轴心来实现基本算法,对100000000个随机数元素数组进行排序需要5小时,这正常吗

我的系统规格: Mac OS X Lion 10.7.2 2011 英特尔酷睿i5 2.3 GHz 8GB内存

更新2:所以我认为我的其他方法有问题,因为Narendra能够运行快速排序。下面是我试图运行的完整代码

我已经把这个旧的,现在无关紧要的答案移到了最后

编辑x2 啊哈!我想我已经找到了你糟糕表现的原因。你告诉我们你使用的是随机数据。这是真的。但你没有告诉我们的是,你使用的是一个很小范围的随机值

对我来说,如果您更改这一行,您的代码将非常高效:

anArray[x] = random.nextInt(1000) + 1;
为此:

anArray[x] = random.nextInt();    
这与预期背道而驰,对吗?对较小范围的值进行排序应该更便宜,因为我们需要进行的交换应该更少,对吗?那么为什么会发生这种情况呢?发生这种情况是因为有这么多元素平均值相同,为100000。那么,为什么这会导致如此糟糕的表现呢?好吧,假设你在每一点上都选择了一个完美的轴心值:正好是一半。下面是它的外观:

1000 - Pivot: 500
 - 500+ - Pivot: 750
   - 750+ - Pivot: 875
   - 750- - Pivot: 625
 - 500- - Pivot: 250
等等。然而,这里有一个关键的部分,您最终将得到一个分区操作,其中每个值都等于分区值。换句话说,将有一个10万个大数字块,其值与您尝试递归排序的值相同。这将如何发生?它将递归10万次,只删除每个级别的单个轴心值。换句话说,它将把所有的东西都划分到左边或右边

在上面的分解图的基础上进行扩展,它看起来有点像这样,为了简单起见,我使用了8的幂2,请原谅这个糟糕的图形表示法

Depth Min  Max  Pvt NumElements

0     0     7    4   100 000 000
1     0     3    2    50 000 000    
2     0     1    1    25 000 000
3     0     0    0    12 500 000 < at this point, you're
4     0     0    0    12 499 999 < no longer dividing and
5     0     0    0    12 499 998 < conquering effectively.
3     1     1    1    12 500 000
4     1     1    1    12 499 999
5     1     1    1    12 499 998
2     2     3    3    25 000 000
3     ...    
3     ...    
1     4     7    6    50 000 000    
2     4     5    5    25 000 000
3     ...
3     ...    
2     6     7    7    25 000 000
3     ...
3     ... 
如果您想解决这个问题,您需要优化您的代码以减少这种情况的影响。我希望以后会有更多的信息

…然后继续。解决问题的一个简单方法是检查数组是否已在每个步骤中排序

public static void quickSort(int anArray[], int position, int pivot) {

    if (isSorted(anArray, position, pivot + 1)) {
        return;
    }

    //...
}


private static boolean isSorted(int[] a, int start, int end) {
    for (int i = start+1; i < end; i++) {
        if (a[i] < a[i-1]) {
            return false;
        }
    }
    return true;
}
加上这一点,你就不会不必要地重复出现,你应该是金色的。事实上,与在整数的所有32位上随机化值相比,您可以获得更好的性能

只为子孙后代的古老答案 我觉得你的分区逻辑很可疑。让我们提取并忽略交换逻辑。以下是您所拥有的:

    int i = position - 1; 

    for(int j = position; j < pivot; j++ ) {

        if(anArray[j] <= x) {
             i = i + 1;
             swap(anArray, i, j);
        } 

    }
我根本看不出这是怎么回事。例如,如果第一个值小于轴心值,它将与自身交换

我想你想要这样的东西只是一个草图:

for ( int i = 0, j = pivot - 1; i < j; i++ ) {

   if ( anArray[i] > pivotValue ) {
      //i now represents the earliest index that is greater than the pivotValue,
      //so find the latest index that is less than the pivotValue
      while ( anArray[j] > pivotValue ) {
         //if j reaches i then that means that *all* 
         //indexes before i/j are less than pivot and all after are greater
         //and so we should break out here
         j--;
      }

      swap(anArray, i, j);
   }
} 

//swap pivot into correct position
swap(anArray, pivot, j+1);
编辑 我想我理解了最初的分区逻辑,现在我把if块弄糊涂了,认为它是在看大于枢轴的元素。我将保留我的答案,因为它可能会提供更好的性能,但我怀疑它是否会产生重大影响。

我将旧的、现在不相关的答案移到了最后

编辑x2 啊哈!我想我已经找到了你糟糕表现的原因。你告诉我们你使用的是随机数据。这是真的。但你没有告诉我们的是,你使用的是一个很小范围的随机值

对我来说,如果您更改这一行,您的代码将非常高效:

anArray[x] = random.nextInt(1000) + 1;
为此:

anArray[x] = random.nextInt();    
这与预期背道而驰,对吗?对较小范围的值进行排序应该更便宜,因为我们需要进行的交换应该更少,对吗?那么为什么会发生这种情况呢?发生这种情况是因为有这么多元素平均值相同,为100000。那么,为什么这会导致如此糟糕的表现呢?好吧,假设你在每一点上都选择了一个完美的轴心值:正好是一半。下面是它的外观:

1000 - Pivot: 500
 - 500+ - Pivot: 750
   - 750+ - Pivot: 875
   - 750- - Pivot: 625
 - 500- - Pivot: 250
等等。然而,这里有一个关键的部分,您最终将得到一个分区操作,其中每个值都等于分区值。换句话说,将有一个10万个大数字块,其值与您尝试递归排序的值相同。这将如何发生?它将递归10万次,只删除每个级别的单个轴心值。换句话说,它将把所有的东西都划分到左边或右边

在上面的分解图的基础上进行扩展,它看起来有点像这样,为了简单起见,我使用了8的幂2,请原谅这个糟糕的图形表示法

Depth Min  Max  Pvt NumElements

0     0     7    4   100 000 000
1     0     3    2    50 000 000    
2     0     1    1    25 000 000
3     0     0    0    12 500 000 < at this point, you're
4     0     0    0    12 499 999 < no longer dividing and
5     0     0    0    12 499 998 < conquering effectively.
3     1     1    1    12 500 000
4     1     1    1    12 499 999
5     1     1    1    12 499 998
2     2     3    3    25 000 000
3     ...    
3     ...    
1     4     7    6    50 000 000    
2     4     5    5    25 000 000
3     ...
3     ...    
2     6     7    7    25 000 000
3     ...
3     ... 
如果您想解决这个问题,您需要优化您的代码以减少这种情况的影响。我希望以后会有更多的信息

…然后继续。解决问题的一个简单方法是检查数组是否已在每个步骤中排序

public static void quickSort(int anArray[], int position, int pivot) {

    if (isSorted(anArray, position, pivot + 1)) {
        return;
    }

    //...
}


private static boolean isSorted(int[] a, int start, int end) {
    for (int i = start+1; i < end; i++) {
        if (a[i] < a[i-1]) {
            return false;
        }
    }
    return true;
}
加上这一点,你就不会不必要地重复出现,你应该是金色的。事实上,与在整数的所有32位上随机化值相比,您可以获得更好的性能

只为子孙后代的古老答案 你的党 在我看来,逻辑推理真的很可疑。让我们提取并忽略交换逻辑。以下是您所拥有的:

    int i = position - 1; 

    for(int j = position; j < pivot; j++ ) {

        if(anArray[j] <= x) {
             i = i + 1;
             swap(anArray, i, j);
        } 

    }
我根本看不出这是怎么回事。例如,如果第一个值小于轴心值,它将与自身交换

我想你想要这样的东西只是一个草图:

for ( int i = 0, j = pivot - 1; i < j; i++ ) {

   if ( anArray[i] > pivotValue ) {
      //i now represents the earliest index that is greater than the pivotValue,
      //so find the latest index that is less than the pivotValue
      while ( anArray[j] > pivotValue ) {
         //if j reaches i then that means that *all* 
         //indexes before i/j are less than pivot and all after are greater
         //and so we should break out here
         j--;
      }

      swap(anArray, i, j);
   }
} 

//swap pivot into correct position
swap(anArray, pivot, j+1);
编辑
我想我理解了最初的分区逻辑,现在我把if块弄糊涂了,认为它是在看大于枢轴的元素。我将保留我的答案,因为它可能会提供更好的性能,但我怀疑它是否会产生重大影响。

作为一名c语言人员,我刚刚将上述代码粘贴到一个空的c项目中。 完成一个包含100.000.000个整数的数组需要35秒。 代码似乎没有什么问题,您的环境中肯定有其他东西。Java进程是否允许分配约800 MB的RAM

如果将数组大小降低到10.000.000,会发生什么情况。那你是不是快3秒了? 是否存在某种数组大小导致排序突然变慢

编辑

我几乎可以肯定,您没有随机数组,您的随机初始化可能失败了

如果为每个元素创建一个新的随机对象,通常会为每个元素获得相同的值,因为每次初始化随机对象都会以毫秒为单位对随机生成器进行种子排序。如果整个数组在同一毫秒内初始化,则所有元素将获得相同的值

在c中,我这样初始化

Random r = new Random();
var intArr = (from i in Enumerable.Range(0, 10000)
            select r.Next()).ToArray();
var sw = System.Diagnostics.Stopwatch.StartNew();
quickSort(intArr, 0, intArr.Length - 1);
sw.Stop();
排序需要2毫秒

如果我为每个元素重新初始化随机对象

var intArr = (from i in Enumerable.Range(0, 10000)
              select (new Random()).Next()).ToArray();

我花了300毫秒进行排序,因为数组中的所有元素都得到相同的值。

作为一个c语言的家伙,我刚刚将上述代码粘贴到一个空的c项目中。 完成一个包含100.000.000个整数的数组需要35秒。 代码似乎没有什么问题,您的环境中肯定有其他东西。Java进程是否允许分配约800 MB的RAM

如果将数组大小降低到10.000.000,会发生什么情况。那你是不是快3秒了? 是否存在某种数组大小导致排序突然变慢

编辑

我几乎可以肯定,您没有随机数组,您的随机初始化可能失败了

如果为每个元素创建一个新的随机对象,通常会为每个元素获得相同的值,因为每次初始化随机对象都会以毫秒为单位对随机生成器进行种子排序。如果整个数组在同一毫秒内初始化,则所有元素将获得相同的值

在c中,我这样初始化

Random r = new Random();
var intArr = (from i in Enumerable.Range(0, 10000)
            select r.Next()).ToArray();
var sw = System.Diagnostics.Stopwatch.StartNew();
quickSort(intArr, 0, intArr.Length - 1);
sw.Stop();
排序需要2毫秒

如果我为每个元素重新初始化随机对象

var intArr = (from i in Enumerable.Range(0, 10000)
              select (new Random()).Next()).ToArray();

我需要300毫秒来排序,因为数组中的所有元素都得到相同的值。

你的应用程序消耗了多少内存?是使用交换文件吗?在同一环境中使用java.util.Arrays.sort处理同一数据集需要多长时间?发布您的实现,我们来看看。有各种可能的方法使快速排序比它应该的慢。@nfechner:你认为Java如何推翻操作系统的内存管理方案并防止内存被交换?@Doug:如果堆栈空间不足,那么你的算法几乎肯定有缺陷。quicksort堆栈的平均深度应为日志100000000左右。看起来您没有有效地进行分割或分区。数据是否已基本排序?如果是这样,你需要将索引随机化以用作轴心值。你的应用程序消耗了多少内存?是使用交换文件吗?在同一环境中使用java.util.Arrays.sort处理同一数据集需要多长时间?发布您的实现,我们来看看。有各种可能的方法使快速排序比它应该的慢。@nfechner:你认为Java如何推翻操作系统的内存管理方案并防止内存被交换?@Doug:如果堆栈空间不足,那么你的算法几乎肯定有缺陷。quicksort堆栈的平均深度应为日志100000000左右。看起来您没有有效地进行分割或分区。数据是否已基本排序?如果是这样的话,你需要将索引随机化以用作轴心值。确切地说,它会自动交换。这本书就是这样教它的,《算法导论》第三版。我想我应该改进它。@Doug:我正在重新研究它,看看它是如何工作的。这不是我考虑分区的方式,但我不能说它不起作用。我也不能说它比我的建议慢了多少,所以对我的回答持保留态度。我不想成为一条红鲱鱼的来源:-。此外,您是否尝试过在Java中使用random类在随机的基础上获取pivot?然后查看此算法4-5次运行的平均值。你应该得到更多的性能改进,如果它确实有效的话,b

但交换本身可能是它慢下来的原因,对吧?@Doug:它可能会让事情慢一点。给定一个大小为N的数组,两种分区策略都会进行N次比较,但如果不需要,我的分区策略将永远不会进行交换。不过,这可能会以更复杂、更不可优化的代码为代价。所以我怀疑这是一个巨大的收益。确切地说,它会自己交换。这本书就是这样教它的,《算法导论》第三版。我想我应该改进它。@Doug:我正在重新研究它,看看它是如何工作的。这不是我考虑分区的方式,但我不能说它不起作用。我也不能说它比我的建议慢了多少,所以对我的回答持保留态度。我不想成为一条红鲱鱼的来源:-。此外,您是否尝试过在Java中使用random类在随机的基础上获取pivot?然后查看此算法4-5次运行的平均值。你应该在它确实有效的时候得到更多的性能提升,但是交换本身可能是它慢下来的原因,对吧?@Doug:它可能会让事情慢下来一点。给定一个大小为N的数组,两种分区策略都会进行N次比较,但如果不需要,我的分区策略将永远不会进行交换。不过,这可能会以更复杂、更不可优化的代码为代价。所以我怀疑这是一个巨大的收益。它在1000000和10000000之间运行良好。我的运行时只包括快速排序。创建数组的过程发生在我的时间记录之外。@doug-那么在2000万、3000万等情况下会发生什么呢?运行时间是否像您预期的n*logn算法那样平稳地增长,还是在某个点突然变化?@Albin:Java的随机实现实际上可以防止您描述的情况。以毫秒为单位的时间用于对随机数进行种子设定,是的,但每个实例化也会增加一个计数器,然后将其值添加到种子中。因此,在同一毫秒内创建的两个随机数确实会生成不同的序列。@MarkPeters,啊,这是一个很好的功能。它可以在1000000和10000000中正常工作。我的运行时只包括快速排序。创建数组的过程发生在我的时间记录之外。@doug-那么在2000万、3000万等情况下会发生什么呢?运行时间是否像您预期的n*logn算法那样平稳地增长,还是在某个点突然变化?@Albin:Java的随机实现实际上可以防止您描述的情况。以毫秒为单位的时间用于对随机数进行种子设定,是的,但每个实例化也会增加一个计数器,然后将其值添加到种子中。所以在同一毫秒内创建的两个随机数确实会生成不同的序列。@MarkPeters,啊,这是一个很好的特性。