我目前正在用 Java 编写一个快速排序算法来对随机整数数组进行排序,然后使用 System.nanoTime() 对它们进行计时。这些数组的大小是 10 的幂,从 10^3 开始到 10^7 结束。此外,随机列表具有不同的属性。我正在对纯随机列表、具有某些相同值 (fewUnique) 的列表、反向排序列表、排序列表和几乎排序列表进行排序。
排序有效。它以递归方式对数组执行快速排序,直到需要对数组的 30 个或更少元素进行排序,在这种情况下,它执行插入排序。
对于 10^3 和 10^4 一切都很好,但是一旦我达到 10^5 值,它只会对随机列表、少数唯一列表和随机列表进行排序,但在对几乎已排序和已排序列表进行排序时会导致堆栈溢出错误。
我认为问题不在于列表的生成方式,因为堆栈溢出发生在排序算法中(编译器引用的行是 findPivot() 方法中的第一行)。此外,它通常会在崩溃前对 1 到 6 个列表进行排序。因此,算法本身必须以某种方式与几乎排序和排序的列表交互。此外,反向列表的生成涉及调用用于创建排序列表(然后将其反向)的代码。
此外,我发现问题不太可能只是由于某种原因,算法必须通过递归在几乎排序和排序的列表中调用数组部分的分区,而不是其他列表类型,因为它可以对具有 10^7 个值的随机列表进行排序,这将需要比具有 10^5 个值的几乎排序的列表更多的分区。
我意识到这一定与这些列表类型如何与我的快速排序的递归交互有关,但如果有人能阐明它,那就太好了。我已将代码完整地放入快速排序算法和下面的随机列表生成器中。
快速排序算法
/**
* Performs a quick sort given the indexes of the bounds of an integer array
* @param arr The array to be sorted
* @param highE The index of the upper element
* @param lowE The index of the lower element
*/
public static void quickSort(int[] arr, int highE, int lowE)
{
//Only perform an action if arr.length > 30, otherwise insertion sort [recursive base case])
if (lowE + 29 < highE)
{
//Get the element and then value of the pivot
int pivotE = findPivot(arr, highE, lowE);
int pivotVal = arr[pivotE], storeE = lowE;
//Swap the pivot and the last value.
swapElements(arr, pivotE, highE);
//For each element in the selection that is not the pivot, check to see if it is lower than the pivot and if so, move it to the leftmost untouched element.
for (int i = lowE; i < highE; i++)
{
if (arr[i] < pivotVal)
{
swapElements(arr, storeE, i);
//Increment storeE so that the element that is being switched moves to the right location
storeE++;
}
}
//Finally swap the pivot into its proper position and recrusively call quickSort on the lesser and greater portions of the array
swapElements(arr, storeE, highE);
//Lesser
quickSort(arr, storeE - 1, lowE);
//Greater
quickSort(arr, highE, storeE + 1);
}
else
{
insertSort(arr, highE, lowE);
}
}
/**
* Finds the pivot element
* @param arr The array to be sorted
* @param highE The index of the top element
* @param lowE The index of the bottom element
* @return The index of the pivot.
*/
public static int findPivot(int[] arr, int highE, int lowE)
{
//Finds the middle element
int mid = (int) Math.floor(lowE + (highE - lowE) / 2);
//Returns the value of the median of the first, middle and last elements in the array.
if ((arr[lowE] >= arr[mid]) && (arr[lowE] >= arr[highE]))
{
if (arr[mid] > arr[highE]) {return mid;}
else {return highE;}
}
else if ((arr[mid] >= arr[lowE]) && (arr[mid] >= arr[highE]))
{
if (arr[lowE] > arr[highE]) {return lowE;}
else {return highE;}
}
else
{
if (arr[lowE] > arr[mid]) {return lowE;}
}
return mid;
}
/**
*Performs an insertion sort on part of an array
* @param arr The array to be sorted.
* @param highE The index of the top element.
* @param lowE The index of the low element.
*/
public static void insertSort(int[] arr, int highE, int lowE)
{
//Sorts elements lowE to i in array, with i being gradually incremented.
for (int i = lowE + 1; i <= highE; i++)
{
for (int j = i; j > lowE; j--)
{
if (arr[j] < arr[j - 1])
{
swapElements(arr, j, j-1);
}
else {break;}
}
}
}
随机列表生成器
/**
* Creates a random list
* @param arr The array to place the list inside of
*/
public static void randomList(int[] arr)
{
//Places a random number at each element of the array
for (int i = 0; i < arr.length; i++)
{
arr[i] = (int) Math.floor(Math.random() * RAND_MAX);
}
}
/**
* Creates a nearly sorted list of random numbers
* @param arr the array to place the list inside of
*/
public static void nearSortList(int[] arr)
{
//Creates a sorted list in arr
sortList(arr);
int swaps = (int) (Math.ceil(Math.pow((Math.log(arr.length)), 2.0)));
//The two values to be switched each time
int a, b;
//Performs a number of swaps equal to swaps [log(N) ^ 2] rounded up, with numbers switched no more than ln(N) places away
for (int i = 0; i < swaps; i++)
{
a = (int) Math.floor(Math.random() * arr.length);
b = (int) (a + Math.random() * 2 * Math.log(arr.length) - Math.log(arr.length));
//Accounts for cases in which b is either greater or smaller than the array bounds
if (b < 0)
{
b = -b;
}
else if (b >= arr.length)
{
b = -1 * (arr.length - b);
}
swapElements(arr, a, b);
}
}
/**
* Creates a random list with many unique values in
* @param arr the array to place the list inside of
*/
public static void fewUniqueList(int[] arr)
{
int[] smallArr = new int[(int) Math.floor(Math.pow(arr.length, 9.0 / 10.0))];
//Creates a smaller array of random values
randomList(smallArr);
//Fills the larger list up with values from the smaller list, ensuring aproximately N / N ^ (9/10) instances of each number in the array and ensuring, at most, there are N ^ (9/10) (rounded down) unique values in the large array
for (int i = 0; i < arr.length; i++)
{
arr[i] = smallArr[(int) Math.floor(Math.random() * smallArr.length)];
}
}
/**
* Creates a reversed list of random numbers
* @param arr the array to place the list inside of
*/
public static void reversedList(int[] arr)
{
//Creates a sorted list in arr
sortList(arr);
//Switches each ith elements with its mirror on the other end of the array until the value of i reaches the middle of the array
for (int i = 0; i < (int) (arr.length / 2.0); i++)
{
swapElements(arr, i, arr.length - 1 - i);
}
}
/**
* Creates a sorted list of random numbers using a merge sort
* @param arr the array to place the list inside of
*/
public static void sortList(int[] arr)
{
//Creates a random list in arr
randomList(arr);
Arrays.sort(arr);
}
编辑: [已失效]
编辑 2:
我已经用下面的代码替换了基本的递归调用,以便只调用 EJP 推荐的两个分区中最小的一个,但它仍然没有解决问题。
if (storeE - 1 - lowE < highE - storeE + 1)
{
//Lesser
quickSort(arr, storeE - 1, lowE);
//Greater
quickSort(arr, highE, storeE + 1);
}
else
{
//Greater
quickSort(arr, highE, storeE + 1);
//Lesser
quickSort(arr, storeE - 1, lowE);
}
编辑 3:
好吧,现在很明显,递归深度远远大于我认为的近排序和排序列表。但现在我需要弄清楚为什么会这样,以及为什么随机列表对于 10^7 的值只有 28 的深度,而几乎排序和排序的列表的深度超过 3000
最佳答案
对于随机数组,您可以划分大量数据 block 。
但是对于(几乎)排序的数组,您通常会一次划分 1 个元素。
因此,对于排序数组,您的堆栈大小最终将与数组的大小相同,而对于随机数组,它更有可能是该大小的对数。
因此,即使随机数组比接近排序的数组大得多,较小的数组抛出异常而较大的数组不会抛出异常也就不足为奇了。
修改您的代码
就修复而言,如EJP pointed out ,您应该首先进行较小的分区以限制堆栈增长。但这本身并不能解决问题 Java doesn't support tail-call optimization (好吧,据我所知,它对于实现来说是可选的)。
此处一个相当简单的解决方法是将您的函数放入 while 循环,本质上是对尾调用优化进行硬编码。
为了更好地理解我的意思:
public static void quickSort(int[] arr, int highE, int lowE)
{
while (true)
{
if (lowE + 29 < highE)
{
...
quickSort(arr, storeE - 1, lowE);
// not doing this any more
//quickSort(arr, highE, storeE + 1);
// instead, simply set the parameters to their new values
// highE = highE;
lowE = storeE + 1;
}
else
{
insertSort(arr, highE, lowE);
return;
}
}
}
好吧,既然你有了基本的想法,这看起来会更好(功能上等同于上面的,只是更简洁):
public static void quickSort(int[] arr, int highE, int lowE)
{
while (lowE + 29 < highE)
{
...
quickSort(arr, storeE - 1, lowE);
lowE = storeE + 1;
}
insertSort(arr, highE, lowE);
}
当然这实际上并没有先做小的,但我会把它留给你去弄清楚(看来你已经很清楚如何做)。
这是如何运作的
对于一些虚构的值...
您当前的代码执行此操作:(缩进表示该函数调用内部发生的情况 - 因此增加缩进意味着递归)
quickSort(arr, 100, 0)
quickSort(arr, 49, 0)
quickSort(arr, 24, 0)
insertion sort
quickSort(arr, 49, 26)
insertion sort
quickSort(arr, 100, 51)
quickSort(arr, 76, 0)
insertion sort
quickSort(arr, 100, 74)
insertion sort
修改后的代码是这样做的:
quickSort(arr, 100, 0)
quickSort(arr, 49, 0)
quickSort(arr, 24, 0)
break out of the while loop
insertion sort
lowE = 26
break out of the while loop
insertion sort
lowE = 51
run another iteration of the while-loop
quickSort(arr, 76, 0)
break out of the while loop
insertion sort
lowE = 74
break out of the while loop
insertion sort
增加堆栈大小
不确定你是否考虑过这个,或者它是否适用于你的参数,但你总是可以简单地考虑 increasing the stack size with the -Xss command-line parameter .
关于java - 为什么这种快速排序会导致近排序列表和已排序列表的堆栈溢出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20254261/
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
是否有类似“RVMuse1”或“RVMuselist[0]”之类的内容而不是键入整个版本号。在任何时候,我们都会看到一个可能包含5个或更多ruby的列表,我们可以轻松地键入一个数字而不是X.X.X。这也有助于rvmgemset。 最佳答案 这在RVM2.0中是可能的=>https://docs.google.com/document/d/1xW9GeEpLOWPcddDg_hOPvK4oeLxJmU3Q5FiCNT7nTAc/edit?usp=sharing-知道链接的任何人都可以发表评论
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?