第7章 排序
插入排序由N - 1趟排序组成。对于p = 1到N - 1趟,插入排序保证从位置0到位置p上的元素为已排序状态。插入排序利用了这样的事实:已知位置0到位置p - 1上的元素已经处于排过序的状态。 O(N^2).
1 public static <AnyType extends Comparable<? super AnyType>> 2 void insertionSort(AnyType[] a) 3 { 4 int j; 5 for (int p = 1; p < a.length; p++) 6 { 7 AnyType tmp = a[p]; 8 for (j = p; j > 0 && tmp.compareTo(a[j - 1 ]) < 0; j--) 9 a[j] = a[j - 1]; 10 a[j] = tmp; 11 } 12 }
希尔排序使用一个序列h1,h2,...,ht,叫作增量序列。
1 public static <AnyType extends Comparable<? super AnyType>> 2 void shellSort(AnyType[] a) 3 { 4 int j; 5 for (int gap = a.length / 2; gap > 0; gap /= 2) 6 for (int i = gap; i < a.length; i++) 7 { 8 AnyType tmp = a[i]; 9 for (j = i; j >= gap && tmp.compareTo(a[j - gap]) < 0; j -= gap) 10 a[j] = a[j - gap]; 11 a[j] = tmp; 12 } 13 }
堆排序 O(N logN)
1 public static <AnyType> void swapReferences(AnyType[] a, int index1, int index2) 2 { 3 AnyType tmp = a[index1]; 4 a[index1] = a[index2]; 5 a[index2] = tmp; 6 } 7 8 private static int leftChild(int i) 9 { return 2 * i + 1; } 10 11 private static <AnyType extends Comparable<? super AnyType>> 12 void percDoen(AnyType[] a, int i, int n) 13 { 14 int child; 15 AnyType tmp; 16 17 for (tmp = a[i]; leftChild(i) < n; i = child) 18 { 19 child = leftChild(i); 20 if (child != n - 1 && a[child].compareTo(a[child + 1]) < 0) 21 child++; 22 if (tmp.compareTo(a[child]) < 0) 23 a[i] = a[child]; 24 else 25 break; 26 } 27 a[i] = tmp; 28 } 29 30 private static <AnyType extends Comparable<? super AnyType>> 31 void headSort(AnyType[] a) 32 { 33 for (int i = a.length / 2 - 1; i >= 0; i--) 34 percDoen(a, 0, i); 35 for (int i = a.length - 1; i > 0; i--) 36 { 37 swapReferences(a, 0, i); 38 percDoen(a, 0, i); 39 } 40 }
归并排序 O(N logN)
1 private static <AnyType extends Comparable<? super AnyType>> 2 void mergeSort(AnyType[] a, AnyType[] tmpArray, int left, int right) 3 { 4 if (left < right) 5 { 6 int center = (left + right) / 2; 7 mergeSort(a, tmpArray, left, center); 8 mergeSort(a, tmpArray, center + 1, right); 9 merge(a, tmpArray,left, center + 1, right); 10 } 11 } 12 13 private static <AnyType extends Comparable<? super AnyType>> 14 void merge(AnyType[] a, AnyType[] tmpArray, int leftPos, int rightPos, int rightEnd) 15 { 16 int leftEnd = rightPos - 1; 17 int tmpPos = leftPos; 18 int numElements = rightEnd - leftPos + 1; 19 20 while (leftPos <= leftEnd && rightPos <= rightEnd) 21 if (a[leftPos].compareTo(a[rightPos]) < 0) 22 tmpArray[tmpPos++] = a[leftPos++]; 23 else 24 tmpArray[tmpPos++] = a[rightPos++]; 25 26 while (leftPos <= leftEnd) 27 tmpArray[tmpPos++] = a[leftPos++]; 28 29 while (rightPos <= rightEnd) 30 tmpArray[tmpPos++] = a[rightPos++]; 31 32 for (int i = 0; i < numElements; i++, rightEnd--) 33 a[rightEnd] = tmpArray[rightEnd]; 34 } 35 36 private static <AnyType extends Comparable<? super AnyType>> 37 void mergeSort(AnyType[] a) 38 { 39 AnyType[] tmpArray = (AnyType[])new Comparable[a.length]; 40 mergeSort(a, tmpArray, 0, a.length - 1); 41 }
快速排序
简单的递归排序算法
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public static void sort(List<Integer>items) 5 { 6 if (items.size() > 1) 7 { 8 List<Integer>smaller = new ArrayList<>(); 9 List<Integer>same = new ArrayList<>(); 10 List<Integer>larger = new ArrayList<>(); 11 12 Integer chosenItem = items.get(items.size() / 2); 13 14 for (Integer i : items) 15 { 16 if (i < chosenItem) 17 smaller.add(i); 18 else if (i > chosenItem) 19 larger.add(i); 20 else 21 same.add(i); 22 } 23 24 items.clear(); 25 items.addAll(smaller); 26 items.addAll(same); 27 items.addAll(larger); 28 } 29 }
“经典快速排序”
将数组S排序的基本算法由下列简单的四步组成:
1. 如果S中元素个数是0或1,则返回
2. 取S中任意元素v,称之为枢纽元
3. 将S-{v}(S中其余元素)划分为两个不相交的集合:S1和S2
4. 返回quicksort(S1)后跟v,继而返回quicksort(S2).
7.7.1 选取枢纽元
一种错误的方法:通常、无知的选择是将第一个元素用作枢纽元。
一种安全的做法:一种安全的方针是随机选区枢纽元。
三树中值分割法:一组N个树的中值(也叫作中位数)是第N/2(取上)个最大的数。
1 public static <AnyType extends Comparable<? super AnyType>> 2 void quickSort(AnyType[] a){ quickSort(a, 0, a.length - 1); } 3 4 private static final int CUTOFF = 3; 5 6 private static <AnyType extends Comparable<? super AnyType>> 7 AnyType median3(AnyType[] a, int left, int right) 8 { 9 int center = (left + right) / 2; 10 if (a[center].compareTo(a[left]) < 0) 11 swapReferences(a, left, center); 12 if (a[right].compareTo(a[left]) < 0) 13 swapReferences(a, left, right); 14 if (a[right].compareTo(a[center]) < 0) 15 swapReferences(a, center, right); 16 17 swapReferences(a, center, right - 1); 18 return a[right - 1]; 19 } 20 21 private static <AnyType extends Comparable<? super AnyType>> 22 void quickSort(AnyType[] a, int left, int right) 23 { 24 if (left + CUTOFF <= right) 25 { 26 AnyType pivot = median3(a, left, right); 27 28 int i = left, j = right - 1; 29 for ( ; ; ) 30 { 31 while (a[++i].compareTo(pivot) < 0) { } 32 while (a[--j].compareTo(pivot) > 0) { } 33 if (i < j) 34 swapReferences(a, i, j); 35 else 36 break; 37 } 38 39 swapReferences(a, i, right - 1); 40 41 quickSort(a, left, i - 1); 42 quickSort(a, i + 1, right); 43 } 44 else 45 insertionSort(a, left, right); 46 } 47 48 private static <AnyType extends Comparable<? super AnyType>> 49 void insertionSort(AnyType[] a, int left, int right) 50 { 51 for (int p = left + 1; p <= right; p++) 52 { 53 AnyType tmp = a[p]; 54 int j; 55 56 for (j = p; j > left && tmp.compareTo(a[j - 1]) < 0; j--) 57 a[j] = a[j - 1]; 58 a[j] = tmp; 59 } 60 } 61 62 public static <AnyType> void swapReferences(AnyType[] a, int index1, int index2) 63 { 64 AnyType tmp = a[index1]; 65 a[index1] = a[index2]; 66 a[index2] = tmp; 67 }
7.7.6 选择问题的线性期望时间算法
1. 如果|S|=1,那么k=1并将S中的元素作为答案返回。如果使用小数组的截止(CUTOFF)方法且|S| <= CUTOFF , 则将S排序并返回第k个最小元素。
2. 选取一个枢纽元v属于S。
3. 将集合S-{v} 分割成 S1 和 S2,就像我们在快速排序中所作的那样。
4. 如果 k<=|S1|,那么第k个最小元必然在S1中,在这种情况下,返回quickSelect(S1,k)。如果 k=1+|S1|,那么枢纽元就是第k个最小元,我们将它作为答案返回。 否则,第k个最小元素就在S2中,它是S2中的第(k-|S1|-1)个最小元。我们进行一次递归调用并返回quickSelect(S2,k-|S1|-1)。与快速排序相比,快速选择只作一次递归调用而不是两次。快速选择的最坏情况和快速排序相同,也是O(N^2)。
1 private static <AnyType extends Comparable<? super AnyType>> 2 void quickSelect(AnyType[] a, int left, int right, int k) 3 { 4 if (left + CUTOFF <= right) 5 { 6 AnyType pivot = median3(a, left, right); 7 int i = left, j = right - 1; 8 for ( ; ; ) 9 { 10 while (a[++i].compareTo(pivot) < 0) { } 11 while (a[--j].compareTo(pivot) > 0) { } 12 if (i < j) 13 swapReferences(a, i, j); 14 else 15 break; 16 } 17 swapReferences(a, i, right - 1); 18 19 if (k <= i) 20 quickSelect(a, left, i - 1, k); 21 else if (k > i) 22 quickSelect(a, i + 1, right, k); 23 } 24 else 25 insertionSort(a, left, right); 26 } 27 28 public static <AnyType extends Comparable<? super AnyType>> 29 void quickSelect(AnyType[] a, int k){ quickSelect(a, 0, a.length - 1, k); }
7.11 线性时间的排序:桶排序和基数排序
为使桶排序能够正常工作,必须要有一些附加的信息。输入数据A1,A2,...,AN必须仅有小于M的正整数组成。如果是这种情况,那么算法很简单:使用一个大小为M的称为count的数组,初始化为全0。于是,count有M个单元(或称为桶),初始为空。当读入Ai时,count[Ai]增1。在所有的输入数据被读入后,扫描数组count,打印出排序后的表。
字符串的基数排序的简单实现,用一个ArrayList做桶
1 public static void radixSortA(String[] arr, int stringLen) 2 { 3 final int BUCKETS = 256; 4 ArrayList<String>[] buckets = new ArrayList[BUCKETS]; 5 6 for (int i = 0; i < BUCKETS; i++) 7 buckets[i] = new ArrayList<>(); 8 9 for (int pos = stringLen - 1; pos >= 0; pos--) 10 { 11 for (String s : arr) 12 buckets[s.charAt(pos)].add(s); 13 14 int idx = 0; 15 for (ArrayList<String>thisBucket : buckets) 16 { 17 for (String s : thisBucket) 18 arr[idx++] = s; 19 20 thisBucket.clear(); 21 } 22 } 23 }
定长字符串的计数基数排序
1 public static void countingRadixSort(String[] arr, int stringLen) 2 { 3 final int BUCKETS = 256; 4 5 int N = arr.length; 6 String[] buffer = new String[N]; 7 8 String[] in = arr; 9 String[] out = buffer; 10 11 for (int pos = stringLen - 1; pos >= 0; pos--) 12 { 13 int count[] = new int[BUCKETS + 1]; 14 15 for (int i = 0; i < N; i++) 16 count[in[i].charAt(pos) + 1]++; 17 for (int b = 1; b <= BUCKETS; b++) 18 count[b] += count[b - 1]; 19 for (int i = 0; i < N; i++) 20 out[count[in[i].charAt(pos)]++] = in[i]; 21 22 String[] tmp = in; 23 in = out; 24 out = tmp; 25 } 26 if (stringLen % 2 == 1) 27 for (int i = 0; i < arr.length; i++) 28 out[i] = in[i]; 29 }
变长字符串的基数排序
1 public static void radixSort(String[] arr, int maxLen) 2 { 3 final int BUCKETS = 256; 4 5 ArrayList<String>[] wordsByLength = new ArrayList[maxLen + 1]; 6 ArrayList<String>[] buckets = new ArrayList[BUCKETS]; 7 8 for (int i = 0; i < wordsByLength.length; i++) 9 wordsByLength[i] = new ArrayList<>(); 10 11 for (int i = 0; i < BUCKETS; i++) 12 buckets[i] = new ArrayList<>(); 13 14 for (String s : arr) 15 wordsByLength[s.length()].add(s); 16 17 int idx = 0; 18 for (ArrayList<String>wordsList : wordsByLength) 19 for (String s : wordsList) 20 arr[idx++] = s; 21 22 int startintIndex = arr.length; 23 for (int pos = maxLen - 1; pos >= 0; pos--) 24 { 25 startintIndex -= wordsByLength[pos + 1].size(); 26 27 for (int i = startintIndex; i < arr.length; i++) 28 buckets[arr[i].charAt(pos)].add(arr[i]); 29 30 idx = startintIndex; 31 for (ArrayList<String>thisBucket : buckets) 32 { 33 for (String s : thisBucket) 34 arr[idx++] = s; 35 36 thisBucket.clear(); 37 } 38 } 39 }
7.12 外部排序
小结:对于大部分一般内容的内部排序的应用,选用的方法不是插入排序、希尔排序,归并排序就是快速排序,这主要是由输入的大小以及底层环境来决定的。插入排序适用于非常少量的输入。对于中等规模的输入,希尔排序是个不错的选择。只要增量序列合适,它可以只用少量代码就给出优异的表现。归并排序最坏情况下的表现为O(NlogN),但是需要额外空间。然而,它用到的比较次数是近乎最优的,因为任何仅用元素比较来进行排序的算法都会对某些输入序列必须用至少log(N!)(上限)次比较。快速排序自己并不保证提供这种最坏时间复杂度,并且编程比较麻烦。但是,它可以及几乎肯定地做到O(NlogN),并且跟堆排序组合在一起就可以保证最坏情况下有O(NlogN)。用基数排序可以将字符串在线性时间内排序,这在某些情况下是相对于基于比较的排序法更实际的另一种选择。