各种排序大杂烩
常见排序的时空复杂度
参考:https://www.cnblogs.com/onepixel/articles/7674659.html
1.插入排序
原理:
1、将指针指向某个元素,假设该元素左侧的元素全部有序,将该元素抽取出来,然后按照从右往左的顺序分别与其左边的元素比较,遇到比其大的元素便将元素右移,直到找到比该元素小的元素或者找到最左面发现其左侧的元素都比它大,停止;
2、此时会出现一个空位,将该元素放入到空位中,此时该元素左侧的元素都比它小,右侧的元素都比它大;
3、指针向后移动一位,重复上述过程。每操作一轮,左侧有序元素都增加一个,右侧无序元素都减少一个。
// 插入排序
public static void insertSort(int [] arrays){
for(int i=1;i<arrays.length;i++){
for(int j=i;j>0 && arrays[j]<arrays[j-1];j--){
Result.swap(arrays,j,j-1);
}
}
}
插入排序的速度约比冒泡排序快一倍(比较次数少一倍),比选择排序还要快一些,对于基本有序的数据,插入排序的速度会很快,是简单排序中效率最高的排序算法。
2.希尔排序
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
动画:https://v.youku.com/v_show/id_XMjU4NTcwMDIw.html ,谁说程序员不浪漫!!!!
代码:
// 希尔排序
public static void shellSort(int[] arrays){
int gap=0;
while (gap<=arrays.length){
gap = gap*3+1;
}
while (gap>0){
for (int i = gap; i < arrays.length; i++) {
int j = i -gap;
int temp = arrays[i];
while ((j>=0) && arrays[j]>temp){
arrays[j+gap] = arrays[j];
j -= gap;
}
arrays[j+gap] = temp;
}
gap = (gap-1)/3;
}
}
事实上希尔排序算法在笔试面试中出现的频度也不比直接插入排序高,但它的时间复杂度并不是一个定值,所以偶尔会被面试官问到选择的步长和时间复杂度的关系,要稍微有点了解吧。算法大题中使用该方法或者其思想的题也不多。
3. 选择排序
选择排序可以说是最简单的一种排序方法:
1.找到数组中最小的那个元素
2.将最小的这个元素和数组中第一个元素交换位置
3.在剩下的元素中找到最小的的元素,与数组第二个元素交换位置
重复以上步骤,即可以得到有序数组。
java代码:
// 选择排序比较第一个数据和后面全部的最小值
public static void selectSort(int[] arrays){
for(int i=0;i<arrays.length-1;i++){
int minIndex = i;
for(int j=i+1;j<arrays.length;j++){
if(arrays[j]<arrays[minIndex]){
minIndex=j;
}
}
Result.swap(arrays,minIndex,i);
}
}
本质上:选择排序是冒泡的一种扩展,冒牌排序是一直交换直到元素都在正确的位置上,选择排序是先把最大或最小放在正确的位置上。二者比较次数相同。选择排序交换次数要少一些。
4.堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
数组储存成堆的形式之后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n-2]交换,再对A[0…n-3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。
代码略:主要是建立大根堆、调整大根堆等
5.冒泡排序
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
代码简单
6.快速排序
https://blog.csdn.net/qq_26122557/article/details/79458649
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
7.归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
8.基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点)
基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。
9.桶排序(对于年龄等最大最小值相差不大的排序)
时间复杂度O(n),空间复杂度O(K)
1.找出数列中的最大最小值max.min
2.求最大最小值的差k=max-min+1
3.根据这个差设置k个桶,桶中初始值为0
4.便利数列,根据index = (arr[i]-min)%k得到相应桶的序号,并+1;
5.按照次数输出。
举例: