前面已经学习过简单的排序算法:冒泡排序、选择排序和插入排序,简单排序容易实现但是速度和效率较低,而在地柜中探讨的归并排序速度要快,但是需要的空间是原始数组的两倍,这是一个严重的缺点,所以自然要学习高级排序算法
希尔排序
希尔排序因科学家Donald L.Shell而得名,发明于1959年。希尔排序基于插入排序,但是增加了一个新特性,从而大大提高了插入排序饿的执行效率
插入排序的缺点在于复制的次数太多,但是我们都知道,在数组趋近于有序的时候,插入排序的效率是相当高的,希尔排序就针对此进行了改进
希尔排序相对于插入排序多了一个步长的概念,也就是将原数组首先拆分成间隔相同步长的子数组,然后对子数组进行插入排序,然后缩小步长再进行进一步排序
当步长近似于1时整个数组就趋近于有序了,然后再对整个数组进行排序的效率就能大大提高
示例:(采取每次步长减一)
36 27 14 68 10 20 32 56 41 61
36 20
27 32 <=步长为5
14 56
68 41
10 61
20 27 14 41 10 36 32 56 68 61 第一次排序后
20 10 68
27 36 61 <=步长为4
14 32
41 56
10 27 14 41 20 36 32 56 68 61 第二次排序后
10 41 32 61
27 20 56 <=步长为3
14 36 68
10 20 14 32 27 36 41 56 68 61 第三次排序后
10 14 27 41 68 <=步长为2
20 32 36 56 61
10 20 14 32 27 36 41 56 68 61 第四次排序后 此时已经非常接近有序了
10 14 20 27 32 36 41 56 61 68 步长为1后直接进行插入排序得到有序序列
设计步长每次减一所需要的循环次数可能较多,所以一般是初始步长为数组总长度的一般,然后每次减半,直到1为止
代码
import java.util.Random;
public class Test {
public static void main(String[] args) {
int[] array = new int[10];
Random r = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = r.nextInt(100);
System.out.print(array[i] + " ");
if ((i + 1) % 10 == 0) {
System.out.println();
}
}
sort(array);
for (int i : array) {
System.out.print(i + " ");
}
}
public static void sort(int[] needSortArray) {
int step = needSortArray.length / 2;
int temp;
for (int i = step; i > 0; i /= 2) {
for (int j = 0; j < i; j++) {
for (int k = i; k < needSortArray.length; k += i) {
if (needSortArray[k] < needSortArray[k - i]) {
temp = needSortArray[k];
int l = k - i;
while (l >= 0 && needSortArray[l] > temp) {
needSortArray[l + i] = needSortArray[l];
l -= i;
}
needSortArray[l + i] = temp;
}
}
}
}
}
}
效率
由于希尔排序的特殊性,无法从理论上具体分析出它的效率,通过各种各样基于实验的评估,评估他的时间级从O(N^(3/2))到O(N^(7/6))之
如下图
快速排序
快速排序是目前最流行的排序算法,在大多数情况下,快速排序都是最快的(仅对内部排序或者说随机存储器内的排序而言,对于磁盘文件中的数据进行的排序,其他排序算法可能更好)
快速排序的思想是通过把一个数组划分为两个子数组,然后递归的调用自身为每一个子数组进行快速排序来实现
具体来讲就是首先选取数组中一个数作为基准,使其他数与此基准数作比较,将比基准数小的放在基准数左边,比基准数大的放在基准数右边,然后在分别对基准数左右两侧的数组进行递归调用算法来进一步排序
基准数的选择方式多种多样,可以选择数组起始第一个,也可以选择中间的数,或者可以随机选择
此处为了简单起见选择第一个数作为基准数
代码实现
import java.util.Random;
public class Test {
public static void main(String[] args) {
int[] array = new int[10];
Random r = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = r.nextInt(100);
System.out.print(array[i] + " ");
if ((i + 1) % 10 == 0) {
System.out.println();
}
}
quickSort(array, 0, array.length);
for (int cell : array) {
System.out.print(cell + " ");
}
}
public static void quickSort(int[] needSortArray, int lowerBound,
int upperBound) {
if (lowerBound<0||upperBound>needSortArray.length) {
return;
}
int left = lowerBound;
int right = upperBound - 1;
if (left>right) {
return;
}
int sign = needSortArray[lowerBound];
int temp = 0;
while (left != right) {
while (needSortArray[right] >= sign && right > left) {
right--;
}
while (needSortArray[left] <= sign && left < right) {
left++;
}
if (needSortArray[left] > sign && needSortArray[right] < sign) {
temp = needSortArray[left];
needSortArray[left] = needSortArray[right];
needSortArray[right] = temp;
}
}
needSortArray[lowerBound] = needSortArray[left];
needSortArray[left] = sign;
quickSort(needSortArray, lowerBound, left-1);
quickSort(needSortArray, right + 1, upperBound);
}
}
通常而言,对于小的数组使用插入排序是最快的一种方法,所以如果要压榨快排的性能时,可以考虑当分割出的数组较小时使用插入排序
效率
快速排序饿的时间复杂度为
O(N*logN)
对于分治算法来讲,采用递归的方法把一列数据项分为两组,然后调用自身来分别处理每一组数据项,这种情况下算法实际上是以2为底的,运行时间和N*log2(N)成正比