我正在尝试用Java实现几种排序算法,以比较性能。根据我的阅读,我期望quickSort比mergeSort更快,但在我的代码上它不是,所以我假设我的quickSort算法一定有问题:
public class quickSortExample{
public static void main(String[] args){
Random gen = new Random();
int n = 1000000;
int max = 1500000;
ArrayList<Integer> d = new ArrayList<Integer>();
for(int i = 0; i < n; i++){
d.add(gen.nextInt(max));
}
ArrayList<Integer> r;
long start, end;
start = System.currentTimeMillis();
r = quickSort(d);
end = System.currentTimeMillis();
System.out.println("QuickSort:");
System.out.println("Time: " + (end-start));
//System.out.println(display(d));
//System.out.println(display(r));
}
public static ArrayList<Integer> quickSort(ArrayList<Integer> data){
if(data.size() > 1){
int pivotIndex = getPivotIndex(data);
int pivot = data.get(pivotIndex);
data.remove(pivotIndex);
ArrayList<Integer> smallers = new ArrayList<Integer>();
ArrayList<Integer> largers = new ArrayList<Integer>();
for(int i = 0; i < data.size(); i++){
if(data.get(i) <= pivot){
smallers.add(data.get(i));
}else{
largers.add(data.get(i));
}
}
smallers = quickSort(smallers);
largers = quickSort(largers);
return concat(smallers, pivot, largers);
}else{
return data;
}
}
public static int getPivotIndex(ArrayList<Integer> d){
return (int)Math.floor(d.size()/2.0);
}
public static ArrayList<Integer> concat(ArrayList<Integer> s, int p, ArrayList<Integer> l){
ArrayList<Integer> arr = new ArrayList<Integer>(s);
arr.add(p);
arr.addAll(l);
return arr;
}
public static String display(ArrayList<Integer> data){
String s = "[";
for(int i=0; i < data.size(); i++){
s += data.get(i) + ", ";
}
return (s+"]");
}
}
结果(0到1500000之间的1百万个整数):
mergeSort(也用arrayList实现):1.3sec(平均值)(0.7秒,而int []代替)
quickSort:3秒(平均值)
这只是我的支点的选择,这是不好的,或者算法中也存在一些缺陷。
另外,有没有更快的方法用int []而不是ArrayList()来编码它? (如何为largers / smallers数组声明数组的大小?)
PS:我现在可以以原位方式实现它,因此它使用更少的内存,但这不是重点。
编辑1:我通过更改concat方法获得1秒。 谢谢!
答案 0 :(得分:4)
PS:我现在可以以原位方式实现它,因此它使用更少的内存,但这不是重点。
这不仅仅是使用更少的内存。你在“concat”例程中所做的所有额外工作,而不是做一个正确的就地QuickSort,几乎可以肯定是这么多的成本。如果你仍然可以使用额外的空间,你应该总是编写一个合并排序,因为它比QuickSort更容易进行比较。
想一想:在“concat()”中,你不可避免地需要再次传递子列表,进行更多的比较。如果您就地进行了交换,所有都在一个阵列中,那么一旦您决定交换两个位置,您就不会再做出决定。
答案 1 :(得分:2)
我认为你的快速排序的主要问题就是它没有到位。
两个主要罪魁祸首是smallers
和largers
。 ArrayList的默认大小为10.在对quickSort的初始调用中,一个好的数据透视表示较小的和较大的数字会增长到500,000。由于ArrayList在达到容量时只会增加一倍,因此必须调整大小约19倍。
由于你在每个递归级别上制作一个越来越小的新东西,你将会执行大约2 *(19 + 18 + ... + 2 + 1)的大小调整。大约400个调整大小,ArrayList对象在连接之前必须执行。连接过程可能会执行相似数量的调整。
总而言之,这是一项额外的工作。
糟糕,刚刚注意到data.remove(pivotIndex)
。选择的枢轴索引(数组的中间)也将导致额外的内存操作(即使中间通常是比开始或结束或数组更好的选择)。这就是arraylist会将整个内存块复制到支持数组中左侧枢轴的“右侧”一步。
关于所选枢轴的快速说明,因为您排序的整数均匀分布在n和0之间(如果Random
符合其名称),您可以使用它来选择好的枢轴。也就是说,第一级快速排序应选择max * 0.5作为其枢轴。较小的第二级应选择max * 0.25,而使用较大的第二级应选择max * 0.75(依此类推)。
答案 2 :(得分:1)
我认为,你的算法是非常低效的,因为你正在使用中间数组=更多内存+更多时间进行分配/复制。这是C ++中的代码,但想法是一样的:你必须交换项目,而不是将它们复制到另一个数组
template<class T> void quickSortR(T* a, long N) {
long i = 0, j = N;
T temp, p;
p = a[ N/2 ];
do {
while ( a[i] < p ) i++;
while ( a[j] > p ) j--;
if (i <= j) {
temp = a[i]; a[i] = a[j]; a[j] = temp;
i++; j--;
}
} while ( i<=j );
if ( j > 0 ) quickSortR(a, j);
if ( N > i ) quickSortR(a+i, N-i);
}
答案 3 :(得分:1)
Fundamentals of OOP and data structures in Java By Richard Wiener, Lewis J. Pinson列出了以下快速排序,可能会或可能不会比您的实施更快(我怀疑它)...
public static void quickSort (Comparable[] data, int low, int high) {
int partitionIndex;
if (high - low > 0) {
partitionIndex = partition(data, low, high);
quickSort(data, low, partitionIndex - 1);
quickSort(data, partitionIndex + 1, high);
}
}
private static int partition (Comparable[] data, int low, int high) {
int k, j;
Comparable temp, p;
p = data[low]; // Partition element
// Find partition index(j).
k = low;
j = high + 1;
do {
k++;
} while (data[k].compareTo(p) <= 0 && k < high);
do {
j--;
} while (data[j].compareTo(p) > 0);
while (k < j) {
temp = data[k];
data[k] = data[j];
data[j] = temp;
do {
k++;
} while (data[k].compareTo(p) <= 0);
do {
j--;
} while (data[j].compareTo(p) > 0);
}
// Move partition element(p) to partition index(j).
if (low != j) {
temp = data[low];
data[low] = data[j];
data[j] = temp;
}
return j; // Partition index
}
答案 4 :(得分:0)
我同意原因是不必要的复制。接下来会有更多笔记。
枢轴索引的选择很糟糕,但这不是问题,因为你的数字是随机的。
(int)Math.floor(d.size()/2.0)
相当于d.size()/2
。
data.remove(pivotIndex);
是不必要的n/2
元素复制。相反,您应该检查以下循环是否i == pivotIndex
并跳过此元素。 (嗯,你真正需要做的就是进行排序,但我只是建议直接改进。)
将所有等于pivot的元素放在同一个('较小')部分是个坏主意。想象一下当阵列的所有元素相等时会发生什么。 (同样,在这种情况下不是问题。)
for(i = 0; i < s.size(); i++){
arr.add(s.get(i));
}
相当于arr.addAll(s)
。当然,这里还有不必要的复制。您只需将右侧部分中的所有元素添加到左侧,而不是创建新列表。
(如何为largers / smallers数组声明数组的大小?)
我不确定我是否帮助你,但是你想要array.length
吗?
所以,我认为即使没有实施就地排序,也可以显着提高性能。
答案 5 :(得分:0)
从技术上讲,Mergesort比Quicksort(Θ(n ^ 2)最坏的情况具有更好的时间行为(Θ(nlogn)最差和平均情况)Θ(nlogn)平均情况)。因此,很有可能找到Mergesort优于Quicksort的输入。根据您如何挑选枢轴,您可以使最坏情况变得罕见。但对于Quicksort的简单版本,“最坏情况”将被排序(或几乎排序)数据,这可能是一个相当常见的输入。
Here's what Wikipedia says关于这两个:
在典型的现代建筑中, 高效的快速实施 通常优于mergesort 排序基于RAM的数组。在另一 合并排序是一种稳定的排序, 并行化更好,更多 有效处理缓慢访问 顺序媒体。[引证需要] 合并排序通常是最佳选择 用于排序链表:在此 情况相对容易 以这种方式实现合并排序 它只需要Θ(1)额外 空间和慢速随机访问 链表的性能 一些其他算法(如 快速表演,表现不佳等等 (如heapsort)完全 不可能的。