一、题目
题目:快速排序算法及优化。本实验目的主要研究以下两点:
1、快排算法的两种实现与比较 2、利用插入排序优化快排算法的两种实现与比较,以及测试不同k值对结果的影响
二、算法思想
1、设当前待排序的无序区为R[low..high],利用分治法可将快速排序的基本思想描述为:
①分解:
在R[low..high]中任选一 个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和 R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边 的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
注意:划分的关键是要求出基准记录所在的位置pivotpos。关于如何求pivotpos,本实验采取了两种算法并进行了对比。这两种算法分别是:partition1(), partition()2。其中partiton1()是利用单向遍历数组求出划分元的的位置pivotpos。partion2()是从两头遍历。
②求解:
通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
2、在快速的排序的方法中加入判断语句,当high-low >= k的时候则继续使用快速排序的算法,否则调用插入排序。此处的k取1或者 2时是快排序。
三、实验结果
1、首先对程序中的主要函数进行Junit测试验证程序正确性。结果如下,全部通过。
2、测试实验目的一
3、测试实验目的二
四、结果分析
1、关于划分元位置确定的方法,实验中的结果表明单向确定pivotpos的方法没有双向确定pivotpos的方法性能好。
2、关于使用插入排序改进快排序的算法中的k的最优值选取问题,理论在lg n附近,但是实验结果表明在非理论值附近也可能取到最优的k值。
3、本实验中100000个数据的排序情况下,当使用插入排序改进的快排序算法使用最优值的k时性能好于快排序算法,改进成功。
五、总结
1. 快速排序是一种排序算法,对包含n个数的输入数组最坏运行时间为θ(n*n)。虽然这个最坏情况运行时间比较差,但快速排序通常是用于排序的最佳的实用选择。这是因为其平均性能相当好:期望的运行时间为θ(n*lgn),且θ(n*lgn)记号中隐含的常数因子很小。另外,它还能够进行就地排序,在虚拟环境中也能很好地工作。
2. 对插入排序来说,当输入已“几乎”排好序时,运行时间是很快的。在实践中,可以充分利用这一特点来改善快速排序的运行时间。当在一个长度小于k的子数组上调用快速排序时,让它不做任何排序就返回。当顶层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。从而使整体算法的时间复杂度降为O(n*k+n*lg(n/k))。至于k如何选择,理论上在lg(n)附近选择,实际实践中应该根据n的大小通过实验来确定最优k。
附件:
源程序:1.QuickSortExperiment.java
import java.util.Random;
public class QuickSortExperiment {
/**
* @author :
* @since : 2012/11/02
* last modified: 2012/11/14
*
*/
// 以下变量的值可以根据实验情况酌情修改
public static int ELEMENTS_NUM[] = {100000,500000,1000000};//每次进行快速排序的数组所含元素的个数
public static int K=15;//改进算法中k的取值
public static int COUNT=5; //每次测试采样的次数。各次采样结果求和取平均得到本次的实验结果
public static long time_record[][]=new long[ELEMENTS_NUM.length][4];//保存对本次实验的四个算法进行elumNum中各测试次数所得到的结果时间
/* 生成长度为num的随机数组*/
public static int[] getRandIntArray(int num){
int[] data = new int[num];
Random rd = new Random();
for(int i=0;i<num;i++){
data[i]=rd.nextInt(1000);
}
return data;
}
/* 判断整形数组是否递增有序 */
public static boolean isAscSorted(int a[]){
int length = a.length;
for(int i=1;i<length;i++){
if(a[i]<a[i-1]){
return false;
}
}
return true;
}
/* 获取划分元的位置
* 下面的方法采用的是算法导论书本上采用的方法
* 单向遍历数组确定划分元的位置
*/
public static int partition1(int a[],int low, int high){
int pivotValue=a[high]; //划分元位置
int i=low-1;
int tmp;
for(int j=low;j<high;j++){
if(a[j]<=pivotValue){
i++;
tmp=a[i];
a[i]=a[j];
a[j]=tmp;
}
}
tmp=a[i+1];
a[i+1]=a[high];
a[high]=tmp;
return i+1;
}
/* 获取划分元的位置
* 下面的方法采用的是双向遍历数组确定划分元的位置
*/
public static int partition2(int a[],int low, int high){
int pivotValue = a[low];
while(low < high){
//从右边找比枢纽元素小的的元素
while((low < high) && (a[high] >= pivotValue)){
high--;
}
//交换两个元素
a[low] = a[high];
//从左边找比枢纽元素大的的元素
while((low < high) && (a[low] <= pivotValue)){
low++;
}
a[high] = a[low];
}
a[low] = pivotValue;
return low;
}
/* 插入排序 */
public static void insertSort(int a[], int p, int r){
for (int i = p+1; i <= r; i++) {
int key = a[i];
int j = i - 1;
while(j >= p && a[j] > key){
a[j + 1] = a[j];
j --;
}
a[j + 1] = key;
}
}
/* 使用partition1划分的方式对数组进行快速排序 */
public static void quickSortUsingPartition1(int a[],int low,int high){
if(low < high){
int mid =partition1(a,low,high);
quickSortUsingPartition1(a,low,mid-1);
quickSortUsingPartition1(a,mid+1,high);
}
}
/* 使用partition2划分的方式对数组进行快速排序 */
public static void quickSortUsingPartition2(int a[],int low,int high){
if(low < high){
int mid =partition2(a,low,high);
quickSortUsingPartition2(a,low,mid-1);
quickSortUsingPartition2(a,mid+1,high);
}
}
/* 结合insertSort对partition1划分的方式对数组进行快速排序 */
public static void insertQuickSortUsingPartition1(int a[],int low,int high){
if(low>=high)return;
else if(high-low >= K){
int mid =partition1(a,low,high);
quickSortUsingPartition1(a,low,mid-1);
quickSortUsingPartition1(a,mid+1,high);
}
else{
insertSort(a, low, high);
}
}
/* 结合insertSort对partition2划分的方式对数组进行快速排序 */
public static void insertQuickSortUsingPartition2(int a[],int low,int high){
if(low>=high)return;
else if(high-low >= K){
int mid =partition2(a,low,high);
quickSortUsingPartition2(a,low,mid-1);
quickSortUsingPartition2(a,mid+1,high);
}
else{
insertSort(a, low, high);
}
}
/* 对两种快速排序算法进行测试并比较 */
public static void recordQS_P1(){
int testAry[];//每次进行快速排序的数组
long totalTime,start,end;
for(int i=0;i<ELEMENTS_NUM.length;i++){
int j=1;
totalTime = 0;
while(j<=COUNT){
testAry = getRandIntArray(ELEMENTS_NUM[i]);
start = System.currentTimeMillis();
quickSortUsingPartition1(testAry, 0, ELEMENTS_NUM[i]-1);
end = System.currentTimeMillis();
totalTime = totalTime + (end -start);
j++;
// System.out.println(isAscSorted(testAry));
}
time_record[i][0] = totalTime/COUNT;//取平均
}
}
public static void recordQS_P2(){
int testAry[];//每次进行快速排序的数组
long totalTime,start,end;
for(int i=0;i<ELEMENTS_NUM.length;i++){
int j=1;
totalTime = 0;
while(j<=COUNT){
testAry = getRandIntArray(ELEMENTS_NUM[i]);
start = System.currentTimeMillis();
quickSortUsingPartition2(testAry, 0, ELEMENTS_NUM[i]-1);
end = System.currentTimeMillis();
totalTime = totalTime + (end -start);
j++;
// System.out.println(isAscSorted(testAry));
}
time_record[i][1] = totalTime/COUNT;//取平均
}
}
public static void recordIQS_P1(){
int testAry[];//每次进行快速排序的数组
long totalTime,start,end;
for(int i=0;i<ELEMENTS_NUM.length;i++){
int j=1;
totalTime = 0;
while(j<=COUNT){
testAry = getRandIntArray(ELEMENTS_NUM[i]);
start = System.currentTimeMillis();
insertQuickSortUsingPartition1(testAry, 0, ELEMENTS_NUM[i]-1);
end = System.currentTimeMillis();
totalTime = totalTime + (end -start);
j++;
// System.out.println(isAscSorted(testAry));
}
time_record[i][2] = totalTime/COUNT;//取平均
}
}
public static void recordIQS_P2(){
int testAry[];//每次进行快速排序的数组
long totalTime,start,end;
for(int i=0;i<ELEMENTS_NUM.length;i++){
int j=1;
totalTime = 0;
while(j<=COUNT){
testAry = getRandIntArray(ELEMENTS_NUM[i]);
start = System.currentTimeMillis();
insertQuickSortUsingPartition2(testAry, 0, ELEMENTS_NUM[i]-1);
end = System.currentTimeMillis();
totalTime = totalTime + (end -start);
j++;
// System.out.println(isAscSorted(testAry));
}
time_record[i][3] = totalTime/COUNT;//取平均
}
}
public static void printTime() {
System.out.println("\t\t[快排_单向]\t[快排_双向]\t[插入快排_单向K="+K+"]\t[插入快排_双向K="+K+"]");
for(int i=0;i<ELEMENTS_NUM.length;i++){
System.out.print(ELEMENTS_NUM[i]+"个元素\t");
for(int j=0;j<4;j++){
System.out.print(time_record[i][j]+"\t\t");
}
System.out.println();
}
}
public static void main(String[] args) {
/*
* 实验目的一
*/
System.out.println("实验测试1:采用不同的划分元位置确定方法的快排序和插入快排序对不同元素总数的执行结果。单位:ms");
recordQS_P1();
recordQS_P2();
recordIQS_P1();
recordIQS_P2();
printTime();
/*
* 实验目的二
*/
QuickSortExperiment.ELEMENTS_NUM=new int[]{100000};
QuickSortExperiment.COUNT=20;//提高精度
int somek[]={2,5,7,8,9,10,11,12,13,14,15,
16,17,18,19,20,21,22,24,25,28,30,
35,80,100,500};
time_record=new long[ELEMENTS_NUM.length][4];//清空变量的值
System.out.println("\n实验测试2:K值大小对优化算法结果的影响(以下数据取自100000个数据的插入快排_双向算法执行结果)");
System.out.println("注:log(100000)="+Math.log(100000)/Math.log(2));
System.out.println("K值\t\t时间(ms)");
for(int value:somek){
QuickSortExperiment.K=value;
recordIQS_P2();//调用插入算法改进后的快排序,运行时间保存在 time_record[0][3]
System.out.println(value+"\t\t"+time_record[0][3]);
}
}
}
2.JUnit测试程序:QuickSortExperimentTest.java
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Ignore;
import org.junit.Test;
public class QuickSortExperimentTest {
// private static QuickSortExperiment sample = new QuickSortExperiment();
@Test
public void testGetRandIntArray() {
int testAry[]=QuickSortExperiment.getRandIntArray(100);
assertEquals(100,testAry.length);
}
@Ignore
public void testPartiton1() {
}
@Ignore
public void testPartiton2() {
}
@Ignore
public void testRecordQS_P1() {
}
@Ignore
public void testRecordQS_P2() {
}
@Ignore
public void testRecordIQS_P1() {
}
@Ignore
public void testRecordIQS_P2() {
}
@Test
public void testIsAscSorted() {
int testAry[]=new int[]{22,1,19,2,-98,55,2};
assertFalse(QuickSortExperiment.isAscSorted(testAry));
testAry=new int[]{-9,11,119,222,234,525,752};
assertTrue(QuickSortExperiment.isAscSorted(testAry));
}
@Test
public void testQuickSortUsingPartition1() {
int testAry[] = QuickSortExperiment.getRandIntArray(100);
QuickSortExperiment.quickSortUsingPartition1(testAry,0,99);
assertTrue(QuickSortExperiment.isAscSorted(testAry));
}
@Test
public void testQuickSortUsingPartition2() {
int testAry[] = QuickSortExperiment.getRandIntArray(100);
QuickSortExperiment.quickSortUsingPartition2(testAry,0,99);
assertTrue(QuickSortExperiment.isAscSorted(testAry));
}
@Test
public void testInsertQuickSortUsingPartition1() {
int testAry[] = QuickSortExperiment.getRandIntArray(100);
QuickSortExperiment.insertQuickSortUsingPartition1(testAry,0,99);
assertTrue(QuickSortExperiment.isAscSorted(testAry));
}
@Test
public void testInsertQuickSortUsingPartition2() {
int testAry[] = QuickSortExperiment.getRandIntArray(100);
QuickSortExperiment.insertQuickSortUsingPartition2(testAry,0,99);
assertTrue(QuickSortExperiment.isAscSorted(testAry));
}
}