排序算法
排序算法可以分为内部排序和外部排序。内部排序是数据记录在内存中进行排序。外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。
算法的时间复杂度和空间复杂度
算法稳定性
稳定的算法有:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法有选择排序、快速排序、希尔排序、堆排序。
当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
算法总结
快速排序:
基本步骤
从序列当中选择一个基准数(pivot),在这里我们选择序列当中第一个数最为基准数,将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧。
重复以上步骤,直到所有子集当中只有一个元素为止。
快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。
改良
在改进的快速排序算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。
冒泡排序
基本步骤
比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
1 public class {
2 public static void sort(Comparable[] a) {
3 int N = a.length;
4 for (int i = 0; i < N-1; i++)
5 for (int j = 0; j <N-1-i; j++) {
6 if (less(a[j+1], a[j]))
7 exch(a, j+1, j);
8 }
9 }
10}
冒泡排序是原地排序算法,对于长度为(N)的数组,在最坏的情况下,冒泡排序需要(~N2/2)次比较和(~N2/2)次交换;在最好的情况下,冒泡排序需要(~N^2/2)次比较和(0)次交换。尽管这个算法是最简单了解和实现的排序算法之一,但它对于包含大量的元素的数组排序是很没有效率的。
选择排序
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
基本步骤
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推…
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,直到整个序列按关键码有序。
1 public class SelectionSort implements IArraySort {
2 public int[] sort(int[] sourceArray) throws Exception{
3 int[] arr = Arrays.copyOf(sourceArray,sourceArray.length);
4 // 总共要经过 N-1 轮比较
5 for (int i = 0; i < arr.length - 1; i++) {
6 int min = i;
7
8 // 每轮需要比较的次数 N-i
9 for (int j = i + 1; j < arr.length; j++) {
10 if (arr[j] < arr[min]) {
11 // 记录目前能找到的最小值元素的下标
12 min = j;
13 }
14 }
15
16 // 将找到的最小值和i位置所在的值进行交换
17 if (i != min) {
18 int tmp = arr[i];
19 arr[i] = arr[min];
20 arr[min] = tmp;
21 }
22 }
23 return arr;
24 }
25}
插入排序:
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
1public class InsertSort implements IArraySort {
2 public int[] sort(int[] sourceArray) throws Exception {
3 // 对 arr 进行拷贝,不改变参数内容
4 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
5
6 // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
7 for (int i = 1; i < arr.length; i++) {
8 // 记录要插入的数据
9 int tmp = arr[i];
10 // 从已经排序的序列最右边的开始比较,找到比其小的数
11 int j = i;
12 while (j > 0 && tmp < arr[j - 1]) {
13 arr[j] = arr[j - 1];
14 j--;
15 }
16 // 存在比其小的数,插入
17 if (j != i) {
18 arr[j] = tmp;
19 }
20 }
21 return arr;
22 }
23}
归并排序:
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
基本步骤
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
重复步骤 3 直到某一指针达到序列尾;
将另一序列剩下的所有元素直接复制到合并序列尾。