高级排序
希尔排序:
针对于插入排序来说,复制的次数太多,在标记的左边部分数据项都是排过序的,在每次往右移动,新的数据项要移动到左边正确的位置,造成中间数据项都必须往右移动一位,这步骤对每个数据项都执行了将近N次复制,平均每次移动 N/2,总共是 N2/2次复制。因此排序的执行效率是O(N2)。
希尔排序通过加大插入排序中元素之间的间隔(增量 - h),并在这些间隔中进行插入排序,从而使数据项能大跨度的移动。当这些数据项排过一趟之后,希尔排序减少数据项的间隔再进行排序,依次下去,最终h=1。
public class ShellSort { public static void main(String[] args) { int[] array = { 1, 65, 48, 9, 5, 2, 33, 6, 45, 88, 11, 2, 59, 4 }; int arraySize = array.length; int inner, outer; int temp; int h = 1; while (h <= arraySize / 3) { h = h * 3 + 1; } while (h > 0) { for (outer = h; outer < arraySize; outer++) { temp = array[outer]; inner = outer; while (inner > h - 1 && array[inner - h] >= temp) { array[inner] = array[inner - h]; inner -= h; } array[inner] = temp; } h = (h - 1) / 3; } for (int r : array) { System.out.println(r); } } }
希尔排序比插入排序快很多,当 h 指越大,数据项每一趟排序需要移动的元素个数很少,但数据项移动的距离很长,这是非常有效率的。当 h 减小时,每一趟排序需要移动的元素个数增多,但此时数据项已经很接近它们排序后的最终位置,这对于插入排序可以更好的效率。
例子中生成间隔序列用 h = h * 3 +1,也可以运用其它的间隔序列如:N/2,但有一个绝对条件,逐渐减小的间隔最后一定等于1,因此最后一个排序是一次普通的插入排序。
效率:除了一些特殊的情况下,还没有人能够从理论上分析希尔排序的效率,估计它的时间级从 O(N3/2) 到 O(N7/6)。
划分算法
划分数据就是把数据分为两组,所有大于特定值(pivot)的数据项分为一组,所有小于特定值的在另外一组。
public class ArrayPar { private final int[] array = { 1, 65, 48, 9, 5, 2, 33, 6, 45, 88, 11, 2, 59, 4 }; private final int arraySize = array.length; private int partitionIt(int left, int right, long pivot) { int leftPtr = left - 1; int rightPtr = right + 1; while (true) { while (leftPtr < right && array[++leftPtr] < pivot) ; while (rightPtr > left && array[--rightPtr] > pivot) ; if (leftPtr >= rightPtr) { break; } else { swap(leftPtr, rightPtr); } } return leftPtr; } private void swap(int dex1, int dex2) { int temp; temp = array[dex1]; array[dex1] = array[dex2]; array[dex2] = temp; } public void display() { System.out.print("Sort : "); for (int i : array) { System.out.print(i + " "); } } public void partition() { int pivot = 50; int partDex = partitionIt(0, arraySize - 1, pivot); System.out.println("Partition is at index : " + partDex); } public static void main(String[] args) { ArrayPar ap = new ArrayPar(); ap.partition(); ap.display(); } }
打印结果:
Partition is at index : 11
Sort : 1 4 48 9 5 2 33 6 45 2 11 88 59 65
效率:划分算法的运行时间是O(N),两个指针开始在数组的两端,然后以或大或小的恒定速度相向移动,停止移动并且在移动的过程中交换。当两个指针相遇,划分完成。更特别的是,每一次划分都有N+1或N+2的比较,因为两端指针相遇加起来一共走了N步,但彼此已经越过了,所以会在划分完成之前多出一两次比较。比较的次数不取决于数据如何排列。但交换的次数取决于数据的排列,假设数据是逆序排列,并且把数据划分一半,那么每一对都需要交换,也就是 N/2 次交换。所以对于任意的数据,在一次划分中交换次数都小于N/2。
快速排序
在大多情况下,快速排序都是最快的,执行时间为O(N*logN)。步骤如下:
- 把数组或子数组分为左边一组(小于pivot)和右边一组(大于pivot),以最右边的数据项为pivot。
- 调用自身对左边一组进行排序。
- 再调用自身对右边一组进行排序。
public class QuickSort { private final int[] array = { 1, 65, 48, 9, 5, 2, 33, 6, 45, 88, 11, 2, 59, 4 }; private final int arraySize = array.length; private int partitionIt(int left, int right, long pivot) { int leftPtr = left - 1; int rightPtr = right; while (true) { while (array[++leftPtr] < pivot) ; while (rightPtr > 0 && array[--rightPtr] > pivot) ; if (leftPtr >= rightPtr) { break; } else { swap(leftPtr, rightPtr); } } swap(leftPtr, right); // 以最右边的数据项作为特定值,最后与划分点交换。 return leftPtr; } private void swap(int dex1, int dex2) { int temp; temp = array[dex1]; array[dex1] = array[dex2]; array[dex2] = temp; } public void display() { System.out.print("Sort : "); for (int i : array) { System.out.print(i + " "); } } private void recQuickSort(int left, int right) { if (right - left <= 0) { return; } int pivot = array[right]; int partition = partitionIt(left, right, pivot); recQuickSort(left, partition - 1); recQuickSort(partition + 1, right); } public void quickSort() { recQuickSort(0, arraySize - 1); } public static void main(String[] args) { QuickSort ap = new QuickSort(); ap.quickSort(); ap.display(); } }
以上的快速排序中,存在很大的问题:N个数据项的数组最理想的是每次划分能分成一半一半的数据项,最坏的划分情况是一个子数组只有一个数据项,另一个子数组含有N-1个数据项,每趟都是如此(在划分前已是有序排序,无论正逆,才出现的情况),那么这种情况下算法执行效率则降低到O(N2),还有就是调用递归可能发生溢出。
三数据项取中 划分: