一、快速排序算法
别急,首先给出快速排序一般算法,即快速排序的非随机基数算法。
快速排序算法 - 源码:
/**
* 快速排序算法
*/
package quikesort;
public class QuikeSort {
public static int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
public static void main(String[] args) {
QuikeSort qs = new QuikeSort();
qs.quikeSort(array, 0, array.length - 1);
for(int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
}
/**
* 分解数组,以基准(枢值)划分子数组,递归求解
* @param arr 待排序数组
* @param low 排序的起始位置
* @param high 排序的终止位置
*/
public void quikeSort(int[] arr, int low, int high) {
if(low < high) {
//基准位置
int pivotLoc = partition(arr, low, high);
//以基准划分为两个子数组,递归求解
quikeSort(arr, low, pivotLoc - 1);
quikeSort(arr, pivotLoc + 1, high);
}
}
/**
* 一趟快速排序
* @return 返回当前基准位置
*/
public int partition(int[] arr, int low, int high) {
//确定基准,以此划分数组
int pivotkey = arr[low];
//将小于基准的数交换到左边区域,将大于基准的数交换到右边区域
while(low < high) {
//从右往左,找到小于基准的数便停止,此时high指向该小于基准的数
while (low < high && arr[high] >= pivotkey) {
--high;
}
arr[low] = arr[high];
//从左往右,找到大于基准的数便停止,此时low指向该大于基准的数
while (low < high && arr[low] <= pivotkey) {
++low;
}
arr[high] = arr[low];
}
//一趟交换结束,此时low==high
//将基准放入交换的最后一个位置
arr[low] = pivotkey;
return low;
}
}
·
二、快速排序算法优化 —— 快速排序随机基数算法
当排序的
序列很大 且 较为有序
时,可以进行一定的效率优化。注意仅在当前情况下可提高算法效率。
根据 快速排序算法的性能取决于划分的对称性
,修改基准的选择方法,使其选到中值或者接近中值,从而提高快速排序算法的效率。
采用随机选择策略:
由于未排序的数组中值未知,只能用随机选择方式增大每个元素作为基准的概率,而不是一味的选取相对固定位置上的元素(如a[p]、a[r]),尤其避免了数组本身可能是相对比较有序的情况带来的划分不均。而随机选择可以期望划分是比较对称的。
快速排序随机基数算法 - 源码:
/**
* 快速排序的随机基准算法
*/
package randomquikesort;
import java.util.Random;
public class RandomQuikeSort {
public static int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
public static void main(String[] args) {
RandomQuikeSort qs = new RandomQuikeSort();
qs.randomQuikeSort(array, 0, array.length - 1);
for(int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
}
/**
* 分解数组,以基准(枢值)划分子数组,递归求解
* @param arr 待排序数组
* @param low 排序的起始位置
* @param high 排序的终止位置
*/
public void randomQuikeSort(int[] arr, int low, int high) {
if(low < high) {
//基准位置
int pivotLoc = randomPartition(arr, low, high);
//以基准划分为两个子数组,递归求解
randomQuikeSort(arr, low, pivotLoc - 1);
randomQuikeSort(arr, pivotLoc + 1, high);
}
}
/**
* @see 一趟快速排序
* @return 返回当前基准位置
*/
public int Partition(int[] arr, int low, int high) {
//确定基准,以此划分数组
int pivotkey = arr[low];
//将小于基准的数交换到左边区域,将大于基准的数交换到右边区域
while(low < high) {
//从右往左,找到小于基准的数便停止,此时high指向该小于基准的数
while (low < high && arr[high] >= pivotkey) {
--high;
}
arr[low] = arr[high];
//从左往右,找到大于基准的数便停止,此时low指向该大于基准的数
while (low < high && arr[low] <= pivotkey) {
++low;
}
arr[high] = arr[low];
}
//一趟交换结束,此时low==high
//将基准放入交换的最后一个位置
arr[low] = pivotkey;
return low;
}
/**
* @see 随机获取基准
* @return 返回调用Partition:返回当前基准位置
*/
public int randomPartition(int[] arr, int low, int high) {
Random ran = new Random();
//在当前数组中产生随机基准,且摒弃最后一个元素
int randomPivotkey = ran.nextInt(high - low) + low;
//将随机取得的基准交换到arr[low]以复用原Partition函数
swap(arr, low, randomPivotkey);
return Partition(arr, low, high);
}
/**
* 交换元素
*/
public void swap(int[] arr, int x, int y) {
int tmp = arr[x];
arr[x] = arr[y];
arr[y] = tmp;
}
}
三、效率测试
只在main函数里测试,这里只给出mian函数的代码,其余代码未做修改同上。
一般快速排序算法 测试代码:
/**
* 快速排序算法时间测试
*/
package quikesort;
public class QuikeSortTimeTest {
public static int[] m_arr = new int[10000];
public static void main(String[] args) {
final int N = 10; //测试次数
long[] time = new long[N]; //记录多次的测试时长
for (int j = 0; j < N; j++) {
for (int i = 0; i < m_arr.length; i++) {
m_arr[i] = (int)(Math.random()*100)+1; //随机序列
//m_arr[i] = i; // 有序序列
//System.out.print(m_arr[i] + " ");
}
//System.out.print("\n");
QuikeSort qs = new QuikeSort();
// 开始时间
long startTime = System.currentTimeMillis();
qs.quikeSort(m_arr, 0, m_arr.length - 1);
// 结束时间
long endTime = System.currentTimeMillis();
/*
* for (int i = 0; i < m_arr.length; i++) {
* System.out.print(m_arr[i] + " ");
* }
*/
//System.out.print("\n");
//System.out.println(startTime);
//System.out.println(endTime);
time[j] = endTime - startTime;
}
System.out.print("基数不随机:");
long totalTime = 0;
double averageTime = 0;
//平均测试时长
for(int j = 0; j < N; j++) {
System.out.print(time[j] + " "); // 输出总时长
totalTime += time[j];
}
averageTime = totalTime / N;
System.out.println("\n平均时长:" + averageTime);
}
....非main函数代码同上,省略
}
快速排序随机基数算法 测试代码:
/**
* 快速排序的随机基准算法时间测试
*/
package randomquikesort;
import java.util.Random;
public class RandomQuikeSortTimeTest {
public static int[] m_arr = new int[100000];
public static void main(String[] args) {
final int N = 10; //测试次数
long[] time = new long[N]; //记录多次的测试时长
for (int j = 0; j < N; j++) {
for (int i = 0; i < m_arr.length; i++) {
//m_arr[i] = (int)(Math.random()*10000)+1; //随机序列
m_arr[i] = i; // 有序序列
//System.out.print(m_arr[i] + " ");
}
//System.out.print("\n");
RandomQuikeSort rqs = new RandomQuikeSort();
// 开始时间
long startTime = System.currentTimeMillis();
rqs.randomQuikeSort(m_arr, 0, m_arr.length - 1);
// 结束时间
long endTime = System.currentTimeMillis();
/*
* for (int i = 0; i < m_arr.length; i++) {
* System.out.print(m_arr[i] + " ");
* }
*/
//System.out.print("\n");
//System.out.println(startTime);
//System.out.println(endTime);
time[j] = endTime - startTime;
}
System.out.print("基数随机:");
long totalTime = 0;
double averageTime = 0;
//平均测试时长
for(int j = 0; j < N; j++) {
System.out.print(time[j] + " "); // 输出总时长
totalTime += time[j];
}
averageTime = totalTime / N;
System.out.println("\n平均时长:" + averageTime);
}
//....非main函数代码同上,省略
}
测试过程及结果
这里只给出关键的测试过程及结果。
1. 无序序列(随机序列):
public static int[] m_arr = new int[100000]; //100000个元素的快速排序
for (int i = 0; i < m_arr.length; i++) {
m_arr[i] = (int)(Math.random()*100)+1; //随机序列 1~100
}
多组测试的平均时长大概稳定在16毫秒:
多组测试的平均时长大概稳定在23毫秒:
增加随机范围,扩大序列元素的随机性:
for (int i = 0; i < m_arr.length; i++) {
m_arr[i] = (int)(Math.random()*10000)+1; //随机序列 1~10000
}
测试结果分析
- 对于无序或随机序列的快速排序,一般快速排序算法与快速排序随机基准算法效率基本相同,相差不大,后者耗时略大一点,对于排序元素较少的一般程序而言完全可以忽略。
- 从增大随机范围的测试结果可以看出,序列元素的随机范围影响快速排序的性能,进而证实了快速排序中随机基准排序算法对面大量数据排序的性能提升。
2. 有序序列(或较有序序列):
这里只给出完全有序序列的测试,较有序时的测试结果与此相同。只需在有序序列的基础上修改某些元素值即可。
public static int[] m_arr = new int[10000]; //10000个元素的快速排序
for (int i = 0; i < m_arr.length; i++) {
m_arr[i] = i; // 有序序列
}
多组测试的平均时长大概稳定在13毫秒:
多组测试的平均时长大概稳定在1毫秒:
测试结果分析
- 对于有序或较有序序列的快速排序,序列元素在10000个左右及以上时,该优化算法(快速排序随机基准算法)相对于一般排序算法,效率得以充分体现。且往上元素越多效率提升越明显。
结论
-
当数组元素随机无序时,由于快速排序随机基数算法需要产生随机元素的下标,并与排序序列的首元素交换位置,此时相对于非随机基数的一般性快速排序算法耗时较长,由于数组元素本身随机性较强,其优化算法的基准随机性并未体现。
-
序列元素的随机范围影响快速排序的性能。
-
当数据量大且基本有序时,与一般快速排序算法相比,此时优化算法基准的随机性得以体现,测试结果显示其耗时更短,效率优化提升非常明显。
实际应用
需要排序的大量较有序数据,且该数据较稳定的情况下,应用十分广泛。例如,按照播放量排序的热门歌曲排行榜,一般以一周的周期来更新,本身是在有序的情况下进行的排名变动,且变动相对稳定,歌曲数据量很大,此时用随机基准的快速排序效率就比较高了。诸如此类等等的排行榜,在当今大数据、智能推荐和数据排名分析的背景下,这种较规则排序的应用是非常广泛和重要的。