实验说明
1. (习题7.4-5) 当输入数据已经”几乎有序”时, 使用插入排序算法速度很快。我们可以利用这一特点来提高快速排序的速度。当对一个长度小于k的子数组调用快速排序时,让它不做任何排序就返回。上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。
2.(可选) 在一次分割结束后,将与本次划分元(基准元素)相等的元素聚集在一起,下一次分割时,不在对这部分元素进行分割。实验要求
1. 编程实现算法
2. 随机生成大小为1w, 10w, 100w, 1000w, …… 的数据集, 测试算法的执行时间, 并普通快速排序、库函数(例如: C++中 的sort()函数)进行比较。
目录
一、实验目的
当输入数据已经几乎有序时, 使用插入排序算法速度很快。我们可以利用这一特点来提高快速排序的速度。当对一个长度小于k 的子数组调用快速排序时,让它不做任何排序就返回。上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。通过实验,理解快速排序算法的执行过程,并通过结合插入排序来对算法性能做优化。
二、实验内容
1.编程实现算法
2.随机生成大小为1w, 10w, 100w, 1000w, …… 的数据集, 测试算法的执行时间, 并与快速排序、库函数中的排序函数作比较。
三、实验步骤
1.算法主体框架
public void insertQuickSort(int[] array){
//内部快排接口
quickSort(array, 0, array.length - 1, QUICKSORT_THRESHOLD);
//插入+快排
insertSort(array);
}
以上是算法的主体框架,首先对数组array进行快速排序,当划分的子问题规模小于QUICKSORT_THRESHOLD时,快速排序不做任何处理即退出。当快速排序结束后,数组array基本有序,对其进行插入排序。
private static final int QUICKSORT_THRESHOLD = 286;
上述是对快速排序问题规模的限制QUICKSORT_THRESHOLD的声明,取其值为286,即当子问题规模大于286时,执行快速排序;当子问题规模小于286时,执行插入排序。
2.快速排序的实现
//快排算法
private void quickSort(int[] array, int left, int right, int k)
{
//无序A[p..r]->段有序A[p..r];
if (right - left >= k)
{
int pivot = partition(array, left, right);
quickSort(array, left, pivot - 1, k);
quickSort(array, pivot + 1, right, k);
}
}
//划分算法
private int partition(int[] array, int left, int right)
{
//输出A[p..q-1]<=A[q]<=A[q+1..r]
int x = array[right];
int i = left - 1;
for (int j = left; j < right; ++j)
{
if (array[j] <= x)
{
++i;
exchange(array, i, j);
}
}
exchange(array, i + 1, right);
return i + 1;
}
以上为快速排序算法。通过if (right - left >= k)来让快速排序在子问题规模大于阈值k时执行。在划分操作中选取子数组的最后一个元素作为数轴pivot元素,按该元素进行划分。
3.插入排序的实现
private void insertSort(int[] array)
{
for (int i = 0; i < array.length; ++i)
{
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key)
{
array[j + 1] = array[j];
--j;
}
array[j + 1] = key;
}
}
上述是插入排序的实现。对于少量元素排序,插入排序是一个有效的算法,是一个从左到右逐步有序的过程。
4.测试程序
public static void main(String[] args)
{
int[] array1 = new int[10000000];
int[] array2 = new int[array1.length];
int[] array3 = new int[array1.length];
//将三个数组做初始化
for (int i = 0; i < array1.length; i++)
{
//0-10000的随机数
array1[i] = (int) (Math.random() * 10000);
array2[i] = array1[i];
array3[i] = array1[i];
}
System.out.println("数组规模为:" + array1.length);
//Method1:快速排序
long timeStart1 = System.currentTimeMillis();
new QuickSort().quickSort(array1, 0, array1.length - 1);
long timeEnd1 = System.currentTimeMillis();
System.out.println("QuickSort耗时:" + (timeEnd1 - timeStart1) + "ms");
//Method2:插入排序+快速排序
long timeStart2 = System.currentTimeMillis();
new InsertQuickSort().insertQuickSort(array2);
long timeEnd2 = System.currentTimeMillis();
System.out.println("InsertQuickSort耗时:" + (timeEnd2 - timeStart2) + "ms");
//Method3:库函数
long timeStart3 = System.currentTimeMillis();
Arrays.sort(array3);
long timeEnd3 = System.currentTimeMillis();
System.out.println("Arrays.sort()耗时:" + (timeEnd3 - timeStart3) + "ms");
//验证正确性
for (int i = 0; i < array2.length; i++)
{
//若不相等则输出error, 说明自建程序错误.
if (array2[i] != array3[i]) System.out.println("Error!");
}
}
在测试程序中,将实验算法分别与快速排序和库函数Arrays.sort()作比较,输出三种方法对同一数组的排序所用时间。
四、实验结果
1.正确性检测
正确性检测的代码如上所示,其中对array3的排序使用的是库函数Arrays.sort(),将其结果与自建算法作比较。结果如下图所示,可说明自建算法排序的正确性。
2.实验结果
选取数组规模分别为1w,10w,100w,1000w做测试,结果如下。
由于数组为随机生成,故数据的特征对排序时间有很大影响,以上均为同规模下多次测试取典型值。可见:
1.在小规模数据的情况下快排与库函数表现较好;
2.在百万级数据的情况下,混合算法表现比较好;
3.在极大规模数据的情况下,库函数表现优异,而混合算法和快排性能下降明显。
五、实验心得
实验中测试了快速排序、混合算法和库函数的性能,可以看出在不同的规模下使用不同的排序算法会得到较好的性能。同时通过查看源码,也简单了解到Java库函数的排序算法的实现方法,并不只是单纯的快速排序+插入排序的组合,还涉及对各种情况的优化,如会检查数据是否已经大致有序等,还采用的单轴快排与双轴快排结合的方式。
六、实验源代码
InsertQuickSort.java
/*
* @Title:
* @Package
* @Description:
* @author yangf257
* @date 2021/11/3 13:06
*/
public class InsertQuickSort
{
private static final int QUICKSORT_THRESHOLD = 286;
public void insertQuickSort(int[] array)
{
quickSort(array, 0, array.length - 1, QUICKSORT_THRESHOLD);
insertSort(array);
}
//快排算法
private void quickSort(int[] array, int left, int right, int k)
{
//无序A[p..r]->段有序A[p..r];
if (right - left >= k)
{
int pivot = partition(array, left, right);
quickSort(array, left, pivot - 1, k);
quickSort(array, pivot + 1, right, k);
}
}
//划分算法
private int partition(int[] array, int left, int right)
{
//输出A[p..q-1]<=A[q]<=A[q+1..r]
int x = array[right];
int i = left - 1;
for (int j = left; j < right; ++j)
{
if (array[j] <= x)
{
++i;
exchange(array, i, j);
}
}
exchange(array, i + 1, right);
return i + 1;
}
private void insertSort(int[] array)
{
for (int i = 0; i < array.length; ++i)
{
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key)
{
array[j + 1] = array[j];
--j;
}
array[j + 1] = key;
}
}
private void exchange(int[] array, int i, int j)
{
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
QuickSort.java
/*
* @Title:
* @Package
* @Description:
* @author yangf257
* @date 2021/11/3 12:54
*/
public class QuickSort
{
public void quickSort(int[] array, int p, int r)
{
//无序A[p..r]->有序A[p..r];
if (p < r)
{
int q = partition(array, p, r);
quickSort(array, p, q - 1);
quickSort(array, q + 1, r);
}
}
private int partition(int[] array, int p, int r)
{
//输出A[p..q-1]<=A[q]<=A[q+1..r]
int x = array[r];
int i = p - 1;
for (int j = p; j < r; ++j)
{
if (array[j] <= x)
{
++i;
exchange(array, i, j);
}
}
exchange(array, i + 1, r);
return i + 1;
}
private void exchange(int[] array, int i, int j)
{
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
Test.java
/*
* @Title:
* @Package
* @Description:
* @author yangf257
* @date 2021/11/3 13:05
*/
import java.util.Arrays;
public class Test
{
public static void main(String[] args)
{
int[] array1 = new int[10000000];
int[] array2 = new int[array1.length];
int[] array3 = new int[array1.length];
//将三个数组做初始化
for (int i = 0; i < array1.length; i++)
{
//0-10000的随机数
array1[i] = (int) (Math.random() * 10000);
array2[i] = array1[i];
array3[i] = array1[i];
}
System.out.println("数组规模为:" + array1.length);
//Method1:快速排序
long timeStart1 = System.currentTimeMillis();
new QuickSort().quickSort(array1, 0, array1.length - 1);
long timeEnd1 = System.currentTimeMillis();
System.out.println("QuickSort耗时:" + (timeEnd1 - timeStart1) + "ms");
//Method2:插入排序+快速排序
long timeStart2 = System.currentTimeMillis();
new InsertQuickSort().insertQuickSort(array2);
long timeEnd2 = System.currentTimeMillis();
System.out.println("InsertQuickSort耗时:" + (timeEnd2 - timeStart2) + "ms");
//Method3:库函数
long timeStart3 = System.currentTimeMillis();
Arrays.sort(array3);
long timeEnd3 = System.currentTimeMillis();
System.out.println("Arrays.sort()耗时:" + (timeEnd3 - timeStart3) + "ms");
//验证正确性
for (int i = 0; i < array2.length; i++)
{
//若不相等则输出error, 说明自建程序错误.
if (array2[i] != array3[i]) System.out.println("Error!");
}
}
}