Java 排序数组的快速排序堆栈溢出(适用于其他数据集)
因此,我尽最大努力优化我的快速排序算法,使其尽可能高效地运行,即使是对于已排序或接近排序的数组,也可以使用三个值的中位数作为轴心,并对较小的分区大小使用插入排序。我已经对随机值的大数组测试了我的代码,它可以工作,但是当我传递一个已经排序的数组时,我得到了一个堆栈溢出错误(讽刺的是,它让我找到了这个网站)。我认为这是我的递归调用的一个问题(我知道分区至少适用于其他数据集),但我不太明白要更改什么 这是我第一学期数据结构课程的一部分,所以任何代码审查都会有所帮助。谢谢Java 排序数组的快速排序堆栈溢出(适用于其他数据集),java,algorithm,data-structures,stack-overflow,quicksort,Java,Algorithm,Data Structures,Stack Overflow,Quicksort,因此,我尽最大努力优化我的快速排序算法,使其尽可能高效地运行,即使是对于已排序或接近排序的数组,也可以使用三个值的中位数作为轴心,并对较小的分区大小使用插入排序。我已经对随机值的大数组测试了我的代码,它可以工作,但是当我传递一个已经排序的数组时,我得到了一个堆栈溢出错误(讽刺的是,它让我找到了这个网站)。我认为这是我的递归调用的一个问题(我知道分区至少适用于其他数据集),但我不太明白要更改什么 这是我第一学期数据结构课程的一部分,所以任何代码审查都会有所帮助。谢谢 public void qui
public void quickSort(ArrayList<String> data, int firstIndex, int numberToSort) {
if (firstIndex < (firstIndex + numberToSort - 1))
if (numberToSort < 16) {
insertionSort(data, firstIndex, numberToSort);
} else {
int pivot = partition(data, firstIndex, numberToSort);
int leftSegmentSize = pivot - firstIndex;
int rightSegmentSize = numberToSort - leftSegmentSize - 1;
quickSort(data, firstIndex, leftSegmentSize);
quickSort(data, pivot + 1, rightSegmentSize);
}
}
public int partition(ArrayList<String> data, int firstIndex, int numberToPartition) {
int tooBigNdx = firstIndex + 1;
int tooSmallNdx = firstIndex + numberToPartition - 1;
String string1 = data.get(firstIndex);
String string2 = data.get((firstIndex + (numberToPartition - 1)) / 2);
String string3 = data.get(firstIndex + numberToPartition - 1);
ArrayList<String> randomStrings = new ArrayList<String>();
randomStrings.add(string1);
randomStrings.add(string2);
randomStrings.add(string3);
Collections.sort(randomStrings);
String pivot = randomStrings.get(1);
if (pivot == string2) {
Collections.swap(data, firstIndex, (firstIndex + (numberToPartition - 1)) / 2);
}
if (pivot == string3) {
Collections.swap(data, firstIndex, firstIndex + numberToPartition - 1);
}
while (tooBigNdx < tooSmallNdx) {
while ((tooBigNdx < tooSmallNdx) && (data.get(tooBigNdx).compareTo(pivot) <= 0)) {
tooBigNdx++;
}
while ((tooSmallNdx > firstIndex) && (data.get(tooSmallNdx).compareTo(pivot) > 0)) {
tooSmallNdx--;
}
if (tooBigNdx < tooSmallNdx) {// swap
Collections.swap(data, tooSmallNdx, tooBigNdx);
}
}
if (pivot.compareTo(data.get(tooSmallNdx)) >= 0) {
Collections.swap(data, firstIndex, tooSmallNdx);
return tooSmallNdx;
} else {
return firstIndex;
}
}
public void快速排序(ArrayList数据、int firstIndex、int numberToSort){
if(第一索引<(第一索引+数字排序-1))
if(numberToSort<16){
insertionSort(数据、第一个索引、numberToSort);
}否则{
int pivot=分区(数据、第一个索引、numberToSort);
int leftSegmentSize=pivot-firstIndex;
int rightSegmentSize=numberToSort-leftSegmentSize-1;
快速排序(数据、firstIndex、leftSegmentSize);
快速排序(数据,透视+1,右段大小);
}
}
公共int分区(ArrayList数据、int firstIndex、int numberToPartition){
int Toobingndx=firstIndex+1;
int Toosmalndx=firstIndex+numberToPartition-1;
字符串string1=data.get(firstIndex);
字符串string2=data.get((firstIndex+(numberToPartition-1))/2);
string3=data.get(firstIndex+numberToPartition-1);
ArrayList RandomString=新的ArrayList();
randomStrings.add(string1);
randomStrings.add(string2);
randomStrings.add(string3);
Collections.sort(随机字符串);
字符串轴=randomStrings.get(1);
if(pivot==string2){
swap(数据,firstIndex,(firstIndex+(numberToPartition-1))/2);
}
if(pivot==string3){
交换(数据,firstIndex,firstIndex+numberToPartition-1);
}
while(tooBigNdx0)){
Toosmalndx--;
}
if(tooBigNdx=0){
集合.swap(数据、firstIndex、tooSmallNdx);
返回smallndx;
}否则{
返回第一个索引;
}
}
在您的分区
方法中,您有时会使用超出范围的元素:
String string1 = data.get(firstIndex);
String string2 = data.get((firstIndex + (numberToPartition - 1)) / 2);
String string3 = data.get(firstIndex + numberToPartition - 1);
(firstIndex+(numberToPartition-1))/2
不是中间元素的索引。这将是(firstIndex+(firstIndex+(numberToPartition-1))/2
=firstIndex+((numberToPartition-1)/2)
事实上,如果firstIndex>n/2
(其中n
是输入中的元素数),则使用的元素的索引小于firstIndex
。对于排序数组,这意味着您选择firstIndex
处的元素作为透视元素。因此,您可以在中获得递归深度
ω(n)“>
这会导致足够大的输入出现堆栈溢出。您可以避免堆栈溢出,而无需对算法进行太多更改。诀窍是对最大分区进行尾部调用优化,只对最小分区使用递归。这通常意味着您必须将
if
更改为while
。我无法真正测试java代码,但它应该看起来像:
public void quickSort(ArrayList<String> data, int firstIndex, int numberToSort) {
while (firstIndex < (firstIndex + numberToSort - 1))
if (numberToSort < 16) {
insertionSort(data, firstIndex, numberToSort);
} else {
int pivot = partition(data, firstIndex, numberToSort);
int leftSegmentSize = pivot - firstIndex;
int rightSegmentSize = numberToSort - leftSegmentSize - 1;
//only use recursion for the smallest partition
if (leftSegmentSize < rightSegmentSize) {
quickSort(data, firstIndex, leftSegmentSize);
firstIndex = pivot + 1;
numberToSort = rightSegmentSize;
} else {
quickSort(data, pivot + 1, rightSegmentSize);
numberToSort = leftSegmentSize;
}
}
}
public void快速排序(ArrayList数据、int firstIndex、int numberToSort){
而(firstIndex<(firstIndex+numberToSort-1))
if(numberToSort<16){
insertionSort(数据、第一个索引、numberToSort);
}否则{
int pivot=分区(数据、第一个索引、numberToSort);
int leftSegmentSize=pivot-firstIndex;
int rightSegmentSize=numberToSort-leftSegmentSize-1;
//仅对最小分区使用递归
如果(leftSegmentSize
这将确保调用堆栈大小最多为
O(log n)
,因为每次调用时,您只能对最多为n/2
大小的数组使用递归。谢谢!这完全有道理。我的代码现在可以正常工作了。如果您有时间,我还有一个问题。对于我的项目,我必须使算法尽可能高效。rand.nextInt()会吗为了找到三个随机值来为未排序的数据找到更好的中位数,有助于优化我的代码以获得n的大值?或者有没有更有效的方法来找到半精确的中位数?@NateSchellink:我个人更喜欢随机版本的quicksort,因为你无法得到最坏情况的输入。(最坏的情况仍然可能发生,但这取决于生成的随机数)。也不要再重新创建3元素ArrayList
。如果不调用sort
,也不难找到3的中间值,这将避免调用该方法的开销。感谢您的响应,我相信您在某些情况下是对的,这将阻止堆栈溢出,但我想要一个更优雅的解决方案开启。这将在所有情况下阻止堆栈溢出。这将为máximum调用堆栈大小设置理论上限。但我理解你的观点,实现中间值也降低了算法运行时的复杂性。