排序的概念
- 排序:使一串记录,按照其中某个或某些关键字的大小,递增或递减的排列起来的操作
- 稳定性:假定在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,arr[ i ]=arr[ j ,且]arr[ i ]在arr[ j ]之前,而在排序后的序列中,arr[ i ]仍然在arr[ j ]之前,则称这种排序算法是稳定的,反之则不稳定。
- 内部排序:数据元素全部放在内存中的排序
- 外部排序:数据元素太多,不能同时存放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
常见的排序有:
- 插入排序:直接插入排序,希尔排序
- 选择排序:选择排序,堆排序
- 交换排序:冒泡排序,快速排序
- 归并排序:归并排序
- 非比较排序:计数排序
插入排序
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止。
直接插入排序
当插入第 i (i >= 1)个元素时,前边的arr[ 0 ],arr[ 1 ],…,arr[ i - 1]已经排好序,此时用arr[ i ]的排序码与arr[ i - 1 ],arr[ i - 2 ] …的排序码进行比较,找到插入位置即将其插入,原来的元素顺序后移,代码如下:
1 | void insert_Sort(int* arr, int size) |
特点:
- 时间复杂度:最坏O(N2),平均O(N2),最好O(N)
- 空间复杂度O(1)
- 稳定性:稳定
- 数据敏感性:敏感
希尔排序
直接插入排序的优化版本,在直接插入排序的基础上增加了步长这一概念,将待排序序列按照步长分成小组,组内进行插入排序,缩短步长,进行多轮排序。
代码如下:
1 | void shell_Sort(int* arr, int size) |
特点:
- 时间按复杂度:最坏O(N1.3),平均O(N1.3),最好O(N)
- 空间复杂度O(1)
- 稳定性,不稳定,分组时相同值元素不一定可以分到同一组,预排序时可能导致相对位置发生变化
- 数据敏感性:敏感
选择排序
从待排序的数据元素中,选出一个最小/最大的元素,存放在序列的起始位置,直到全部待排序的数据元素排完
直接选择排序
在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素,若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交,在剩余的array[ i ]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
代码如下:
1 | void select_Sort(int* arr, int size) |
特点:
- 时间复杂度:最坏O(N2),平均O(N2),最好O(N2)
- 空间复杂度:O(1)
- 稳定性:稳定
- 敏感性:不敏感
优化版本:
1 | void select2_Sort(int* arr, int size) |
堆排序
建堆进行排序,升序建大堆,降序建小堆,通过循环删除堆顶元素进行排序
代码如下:
1 | void shiftDown(int* arr, int size, int parent) |
特点:
- 时间复杂度:恒为O(N*Log2N)
- 空间复杂度:O(1)
- 稳定性:不稳定,调整的过程中相对位置可能发生变化
- 数据敏感性:不敏感
交换排序
根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
冒泡排序
进行多轮比较,每次将最值移到末端,代码如下:
1 | void bubble_Sort(int* arr, int size) |
特点:
- 时间复杂度:最坏O(N2),平均O(N2),最好O(N)
- 空间复杂度:O(1)
- 稳定性:稳定
- 敏感性:敏感
快速排序
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。常见的划分方式有,hoare划分,挖坑法,前后指针法。
代码如下:
1 | int getMid(int* arr, int begin, int end) |
特点:
- 时间复杂度:最坏O(N2)(优化后可以避免),平均O(N* Log2N),最好O(N* Log2N)
- 空间复杂度:O( Log2N) 函数调用栈,极端情况O(N)(优化后可避免)
- 稳定性:不稳定
- 敏感性:敏感
非递归实现:
1 | //栈实现非递归 |
归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
代码如下:
1 | void merge(int* arr, int begin, int mid, int end, int* tmp) |
特点:
- 时间按复杂度:(N* Log2N)
- 空间复杂度:O(N)
- 稳定性:稳定
- 敏感性:不敏感
非递归实现:
1 | void merge_Sort_NoR(int* arr, int size) |
非比较排序
计数排序,统计相同元素出现次数,根据统计结果将序列回收到原有序列中,它适合小范围数据,若范围大,则空间复杂度较高。
代码如下:
1 | void count_Sort(int* arr, int size) |
特点:
- 时间复杂度:O(MAX(N,范围))
- 空间复杂度:O(范围)
- 稳定性:一般来说稳定
- 敏感性:不敏感