插入排序
基本思想:维护一个已排好序的序列,每次从未排好序的序列中选择一个元素插入排好序的序列中。具体来说,初始已排序序列为第一个元素,从第二个元素开始,每次选择一个未排序元素,将已排序序列中所有大于该元素的元素向后移动一位,然后把该未排序元素放到排序序列的正确位置。该算法平均时间复杂度O(n^2),在元素已排好序的情况下,时间复杂度为O(n),空间复杂度O(1),是一种稳定排序算法。
1 | void insert_sort(int a[], int n) { |
希尔(Shell)排序
基本思想:希尔排序是对插入排序的优化,其依据如下。
- 当数据规模较小时,插入排序效率较高(n与n^2差距较小)
- 当元素基本有序时,插入排序效率较高
基于以上事实,希尔排序的过程为:
- 先将所有待排序元素划分为较小的子序列,子序列中相邻元素间的距离为增量值。
- 对划分的子序列进行插入排序。
- 重复1和2并不断减小增量值,当完成增量为1的排序后,所有元素完成排序
1
2
3
4
5
6
7
8
9
10
11void shell_sort(T arr[], int len) {
int gap, i, j;
T temp;
for (gap = len >> 1; gap > 0; gap >>= 1)
for (i = gap; i < len; i++) {
temp = arr[i];
for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap)
arr[j + gap] = arr[j];
arr[j + gap] = temp;
}
}
选择排序
基本思想:每次从未排序序列中选择最小(大)元素放到已排序序列尾部。具体来说,初始时,所有元素都是未排序的,从第一个元素开始,向后扫描未排序序列,记录最小(大)元素下标,将该下标元素交换到扫描起始位置。由于交换过程中有可能把前面的相等元素交换到后面,所以选择排序算法是不稳定的(例如:5,5,2)。该算法时间复杂度O(n^2),最好最坏都是O(n^2),空间复杂度O(1)。
1 | void select_sort(int a[], int n) { |
冒泡排序
基本思想:元素两两交换,把大的元素交换到后面,每次冒泡都能把未排序序列中的最大元素放到正确的位置上。平均时间复杂度O(n^2),空间复杂度O(1),该排序算法是稳定排序算法。
改进一:设置一个标志,如果某轮冒泡没有发生元素交换,表示所有元素已完成排序,可提前退出循环。
改进二:用一个变量记录最后一次交换的位置坐标,该坐标之后的所有元素已完成排序,可以不再进行交换。
1 | void insert_sort(int a[], int n) { |
快速排序
和归并排序一样是一种分治递归算法
基本思想:每次把一个元素放到正确位置上。具体来说,每次选择一个基准元素,所有大于该基准元素的元素放到前面,所有大于该基准元素的元素移动到该元素后面,然后把基准元素放到正确的位置上。重复该过程,直到所有元素都放到正确位置上。快速排序算法是一种不稳定的排序算法,平均时间复杂度O(NlongN),最坏时间复杂度O(N^2),空间复杂度O(longN)。
1 | int partation(int a[], int l, int r) { |
归并排序
基本思想:归并排序是典型的分治法,每次将两个排好序的序列归并成一个有序序列。初始时每个元素都是一个有序序列,然后每2个元素归并、每4个元素归并……直到归并成一个有序序列。可以采用递归的方法,平均、最好、最坏的时间复杂度都是O(NlongN),空间复杂度O(N)+Olong(N)。
1 | void merge(int a[], int start, int end, int tmp[]) { |
堆排序
基本思想:利用堆的性质,先将所有元素插入大顶堆,在插入的过程中调整堆。循环取出堆顶元素与堆的最后一个叶节点交换,得到最大元素、次大元素……放入有序序列。时间复杂度O(NlongN), 空间复杂度O(1).
1 | void adjust(int a[], int x, int n) { |
二叉树排序
基本思想:利用二叉搜索树(Binary Search Tree,BST)的中序遍历是一个非递减序列的性质。先将所有元素插入二叉搜索树,然后中序遍历该二叉树即可得到非递减序列。为了提高性能,在插入过程中需维持二叉树的平衡
上述的插入排序、选择排序、冒泡排序、快速排序、归并排序、堆排序、二叉树排序都是基于比较的排序,时间复杂度为O(N^2)或O(NlongN)。基于比较的排序时间复杂度下界为O(NlongN)。
非比较排序的时间复杂度可以达到O(N),但是需要额外的空间,非比较排序有计数排序、桶排序、基数排序和希尔排序。
桶排序
基本思想:利用分治/Hash的思想,通过某种映射关系,将待排序元素放到若干个有序的桶中,使得待排序元素基本有序。然后对每个桶内的元素进行排序。最后把所有桶的元素合并到一起就可以得到有序序列。计数排序可以看做桶排序的特殊情况。
计数排序
基本思想:计数排序要求待排序元素是整数,首先找出待排序元素的最小值max和最大值min,开辟max - min + 1个数组元素,然后遍历待排序元素,对遍历元素值对应数组下标计数。最后扫描数组,通过计数值确定元素在排序序列中的位置。
计数排序适用于待排序元素的值包含在一个封闭区间的情况,例如对大量学生成绩排序[0, 100]。
基数排序
基本思想:基数排序也可以看做桶排序的一种,具体步骤如下。
- 初始化基数个桶(例如,对十进制整数排序时,初始化10个桶)
- 从最低位开始依次将元素放到该位数值对应的桶中
- 按照桶的顺序把元素放回
- 重复步骤2和3,直到遍历完所有位数