文章目录
本文对几种常用排序进行总结
基本知识:
- 排序稳定性
- 排序的时间复杂度、空间复杂度
冒泡排序(BubbleSort)
参考:https://juejin.im/post/5cacbbe8e51d456e500f7cd0
- 思路:
它重复地走访要排序的数列,一次比较两个元素,如果第二个数字小,就把交换两个数。
它是简单的选择排序。 - 时间复杂度:时间复杂度最好的情况为O(n),最坏的情况是O(n^2)
- 空间复杂度:O(1)
- 算法稳定性:稳定
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。 - 优化:
(1)数组整体有序:设置标志位,明显如果有一趟没有发生交换(flag = false),说明排序已经完成
(2)数组局部有序:记录一轮下来标记的最后位置,下次从头部遍历到这个位置就Ok
(3)双向冒泡,一轮走访,把最大值放到最右边,同时最小值放到最左边
也可以把上述优化两两组合
//冒泡排序的经典写法, 从前往后,大数下沉
public void BubbleSort(int[] array){
for (int end = array.length-1; end >0; end--) { //外层循环使用end来控制内层循环中极值最终上浮到的位置
// 内层循环用来两两比较并交换
for (int i = 0; i <end ; i++) {
if(array[i]>array[i+1]){
exchange(array,i,i+1);
}
}
}
}
//优化一:处理在排序过程中数组整体已经有序的情况
//假设排序ar[]={1,2,3,4,5,6,7,8,10,9}这组数据,按照上面的排序方式,第一趟排序后将10和9交换已经有序,接下来的8趟排序就是多余的,可以在交换的地方加一个标记,如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去。
public void BubbleArrayImprove1(int[] array){
for (int end = array.length-1; end >0; end--) {
boolean isSorted =false;
for (int i = 0; i <end ; i++) {
if(array[i]>array[i+1]){
exchange(array,i,i+1);
isSorted = true;
}
}
if(isSorted==false){
return;
}
}
}
//优化二:处理局部有序,在遍历过程中可以记下最后一次发生交换事件的位置, 下次的内层循环就到这个位置终止, 可以节约多余的比较操作.
public void BubbleArrayImprove2(int[] array){
int end = array.length-1;//记录这一轮循环最后一次发生交换操作的位置
while(end>0){
int tempEnd = end;//设置这一轮循环结束的位置,因为end可能会变,所以一定把本次循环的end先存下来
for (int i = 0; i <tempEnd ; i++) {//这里不能写end
if(array[i]>array[i+1]){
exchange(array,i,i+1);
end = i;//设置(更新)最后一次发生了交换操作的位置
}
}
// 若这一轮没有发生交换,则证明数组已经有序,直接返回即可
if(end == tempEnd) return ;
}
}
//优化三:双向冒泡
public void BubbleArrayImprove3(int[] array){
int start = 0;
int end = array.length-1;
while(start<end){
for (int i =start; i < end ; i++) {
if(array[i]>array[i+1])exchange(array,i,i+1);
}
end--;
for (int i = end; i >0; i--) {
if(array[i]<array[i-1]){
exchange(array,i,i-1);
}
}
start++;
}
}
选择排序
- 思路
在第一趟遍历N个数据,找出其中最小的数值与第一个元素交换,第二趟遍历剩下的N-1个数据,找出其中最小的数值与第二个元素交换…第N-1趟遍历剩下的2个数据,找出其中最小的数值与第N-1个元素交换,至此选择排序完成。 - 时间复杂度O(n^2)
元素移动次数很少,当表有序时移动次数为0,但比较的次数与表的次序无关,所以时间复杂度始终为O(n^2) - 空间复杂度:**O(1)**原地排序算法
- 稳定性:不稳定
例如5,5,3,第一趟就把第一个5跟3进行交换,两个5的相对位置就变了 - 优化:
每次找到最小值和最大值,放到收尾对应位置,虽然时间复杂度不变,但是运行时间变短了。
//最基本的选择排序
public static void selectedSort(int[] arr){
for (int i = 0; i <arr.length-1 ; i++) {
int min = i;//记录要交换的元素的位置
for (int j = i+1; j < arr.length ; j++) {
if(arr[j]<arr[min]){
min = j;
}
}
exchange(arr,i,min);
}
}
// 优化一:
public static void selectedSortBetter(int[] arr){
for (int i = 0,j=arr.length-1; i < arr.length&&j>i; i++,j--) {
int minIndex = i;
int maxIndex = j;
for(int k = i;k<=j;k++){//不能跟之前一样从i的后一位开始找,如果这样的话,如果i恰好是最大的位,就遗漏了
if(arr[k]<arr[minIndex])minIndex = k;
if(arr[k]>arr[maxIndex]) maxIndex = k;
}
exchange(arr,minIndex,i);//先交换最小值
if(maxIndex == i){//检查一下,maxIndex是否是i,如果是,要改,因为i已经换位置了
maxIndex = minIndex;
}
exchange(arr,maxIndex,j);
}
}
插入排序(InsertionSort)
- 思想
每一步将一个元素,按照其关键字的大小插入到它前面已经排序的子序列中,依此重复,直到插入全部元素!对于基本有序的数组比复杂算法还好用,比冒泡和排序都快 - 时间复杂度
a. 最好情况:序列是升序排列,在这种情况下,需要进行的比较操作需(n-1)次。后移赋值操作为0次。即O(n)
b. 最坏情况:序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。后移赋值操作是比较操作的次数加上 (n-1)次。即O(n^2)
c. 平均时间复杂度:O(n^2) - 空间复杂度O(1)
- 稳定性:稳定
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,算法稳定 - 优化
(1)二分查找插入排序:因为在一个有序区中查找一个插入位置,所以可使用二分查找,减少元素比较次数提高效率。
(2) 希尔排序:如果序列本来就是升序或部分元素升序,那么比较+