排序算法主要评判优劣程度角度:
- 时间空间复杂度
- 比较次数,交换次数
- 是否稳定:值相同能否在排序前后仍然保持相对位置
排序还可以分为内部排序与外部排序。数据正常存储在外存上,如果数据量太大,只能一部分掉入内存先排序。
内部排序主要针对数据全部在内存上,主要关注点使得时间空间复杂度最小就行。
外部排序还需要额外考虑外存数据换入换出的时间开销。
插入排序
算法思想:在已经有序的序列中,插入一个新的值。对于一个未被排序的序列,选定一个点,认为前面已经有序,这个点为需要插入的新的点。序列从前往后按照这种思想遍历一遍就可以有序了。
在有序序列中,插入新的值需要做的事情:
以数组a[i]为插入值,a[0]-a[i-1]已经有序,以递增为例。那么只需要从a[i-1]从后往前找,如果a[i-1]>a[i]就将a[i-1]往后移一个位置,当然a[i]需要存放在一个tem变量里面。直到找到<=或者越界为止。那么这个点的后一个结点就是需要将a[i]放的位置。
当然,寻找插入点由于前面已经有序,可以采取二分查找来进行优化。但其实没差,因为二分查找只能优化寻找位置,而寻找完之后,还是需要完成后移操作。所以时间复杂度在量级看来依旧不变。
空间复杂度:O(1)。
时间复杂度:
- 最好的情况,也就序列已经有序的情况,对于每一个点只需要往前看一下就可以跳出第二层循环。所以最好时间复杂度为O(n).
- 最差的情况,也就是序列刚好逆序,对于每一个点都需要遍历到头,当中遇到每一个点都需要将其往后移一个位置。也就是O(n2).
- 平均时间复杂度O(n2)
稳定性:在往前找的时候,可以设定直到找到<=停下来,所以可以保证稳定。
可以针对链表使用。
void insertSort(int a[], int length){
for (int i = 1; i < length ; i ++){
int tem = a[i];
int j;
for (j = i-1 ; j > -1 && a[j] > tem ; j --){
a[j+1] = a[j];
}
a[j+1] = tem;
}
return;
}
希尔排序
可以认为是插入排序的优化。因为插入排序在序列已经大部分有序的情况下表现还是较为良好。所以希尔排序主要目标是先让序列整体大部分有序,然后最后再插入排序,从而实现优化。
算法过程:设定一个gap值,然后元素下标差值为gap的被认为是同一组,每一组完成插入排序。gap每次/2直到gap为0就停止。所以在最后一次排序gap为1时候,实际上也是插入排序,能够保证这个序列已经大部分有序。
一般来说gap初始设定为排序长度/2.
空间复杂度O(1)
时间复杂度没法判断,只能说上界应该是插入排序O(n2)。经过统计最好情况下能够达到O(n1.3)
稳定性:不稳定。因为可能两个值相同被分到不同的组。后面的就被换到前面。
不可以针对链表使用
void shellSort(int a[], int length){
int gap = length << 1;
for ( ; gap >= 1 ; gap <<= 1){
for (int i = gap ; i < length ; i ++){
int tem = a[i];
int j;
for (j = i - gap ; j > 0 && a[j] > tem ; j -= gap){
a[j + gap] = a[j];
}
a[j+gap] = tem;
}
}
return;
}
冒泡排序
算法思想:我习惯从前比到后,然后最大的冒到最后一个。每一次内部迭代两两比较大的放后面,经过序列长度-1就能够保证最大的一定在最后面。每一次从n序列找最大,n-1序列找最大,…所以显然O(n2)
空间复杂度:O(1)
时间复杂度:如果不进行优化的话,显然时间复杂度稳定O(n2)这个量级。
如果进行优化,也就在内部循环开一个bool判断,如果在这一次循环没有发生交换操作,说明整个序列已经有序,就不需要进行n-1序列比较,算法停止在这就行。进行优化后的最好情况就是序列已经有序,此时只要第一寻找长度n序列最大值发现没有交换就会结束算法,所以最好时间复杂度为O(n)。最差情况也就是根上面一样了O(n2)
稳定性:稳定。
可以使用在链表上。
这个算法应该是排序算法最捞的一个,因为判断次数基本拉满,交换次数应该也是所有算法里面最多的,而且是两两交换,还不是插入排序中的替换。除了能够实现排序这个算法一无是处。
void bubbleSort(int a[], int length){
for (int i = 0; i < length ; i ++){
for (int j = length-1 ; j > i ; j ++){
if (a[j-1] > a[j]){
int tem = a[j];
a[j] = a[j+1];
a[j+1] = tem;
}
}
}
return;
}
快速排序
快速排序如其名,从平均看来算法效率是所有排序算法最高的。同时算法可递归,更近一步能够使用并行优化。
算法思想:与冒泡找最大不同,快排思想是随便来一个数字,确定他在最终排序完成序列的位置。只需要找到一个位置,让他左边的数字都比他小,右边的数字都比他大,那么他在最终排序结果肯定也是这个位置。那么找到这个最终位置,左边根右边还是无序的,用这种思想对于左边右边再进行相同操作,一直递归下去进行就可以。
算法过程:选取最做边的数字作为需要排序的数字,然后进行一次排序,能够获得左右两个未被排序的序列,然后对左右序列一直递归排序即可。递归跳出的条件就是排序区间需要存在。
一次排序的算法思想:
现将需要排序的数存在变量var里
控制两个指针变量初始化为一个左端点low一个右端点high。high先从右边往左走,一旦发现第一个比var小的就停止,然后将其的值赋值给low所在的结点。然后low开始向右走,一旦发现比var大的就停下,然后将其值赋值给high所在的结点。两个交替循环直到low,high指向同一个位置,这个位置就是var的最终位置。
总体排序过程可以使用递归,每一次排序可以视为新开了两个左右子树,然后一直下去。
空间复杂度:取决于递归树的高度。
时间复杂度:最好的情况就是树的高度最矮,也就是需要排序的树每一次都被放在的序列的中点,此时时间复杂度为O(logn)。最差的情况就是序列已经有序,时间复杂度在O(n2). 由于已经有序情况较少,所以总体平均快排时间复杂度在O(nlogn)。同时也是算法最好的一个。
稳定性:不稳定
时间跟空间复杂度都要看树的高度,递归可以看作二叉树,二叉树高度范围在logn以及n2的范围上。所以时间空间复杂度也在这两个量级上。
int sortUnit(int a[], int low, int high){
int tem;
while (low<high){
tem = a[low];
while (low < high && a[high]>= tem)
high--;
a[low] = a[high];
while (low < high && a[low] <= tem)
low++;
a[high] = a[low];
}
a[low] = tem;
return low;
}
void quickSort(int a[], int low, int high){
if(low >= high) return;
int pos = sortUnit(a, 0, length-1);
quickSort(a, 0, pos-1);
quickSort(a, pos+1, length-1);
}