必备排序算法详解(java代码实现,图解,比较等,持续更新中)
参考文章:https://blog.csdn.net/hellozhxy/article/details/79911867
(各种排序的比较)https://blog.csdn.net/mengyue000/article/details/77505666
术语
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度:运行完一个程序所需内存的大小。
注解:In-place: 占用常数内存,不占用额外内存
Out-place: 占用额外内存
比较排序和非比较区别
- 比较排序:
- 常见:快速排序,归并排序,堆排序,冒泡排序等在排序的过程中,元素之间的次序依赖于元素之间的比较,每个数都必须与其他数进行比较的排序算法。
- 特点:适用于各种规模的数据,不在乎数据的分布,比较万金油
- 非比较排序:
- 常见:计数排序,基数排序,桶拍序等通过确定每个元素前的元素个数来进行排序的算法。
- 特点:只有确定每个元素前面元素的个数,即可一次遍历解决,但是由于需要占用空间来确定唯一位置,所以对数据规模和数据分布有一定要求。
1. 冒泡排序
-
思路:
第一步:从第一个元素开始,比较相邻元素后判断是否交换,直到最后一个元素。
第二步:针对所有的元素重复第一步,直到遍历中没有出现交换则结束排序。 -
性能分析:
时间复杂度:
2.1. 最好情况:O(n)
2.2. 平均情况:O(n^2)
2.3. 最坏情况:O(n^2)
空间复杂度:O(1)
稳定性:稳定(相同元素的相对位置不会改变) -
适用场景
- 适用元素较少的情况下,元素太多的话,交换和比较次数都会很多,影响效率,元素多的情况适合用快速排序.
- 当数组基本有序的情况下适合使用冒泡排序和直接插入排序,它们在基本有序的情况下排序的时间复杂度接近O(n).
-
图解:
-
代码实现
int [] bubbleSort(int [] data){ int flag=0; int temp=0; for (int i = 0; i <data.length ; i++) { flag=0; for (int j = 0; j <data.length-i-1 ; j++) { if(data[j]>data[j+1]){ temp =data[j]; data[j]=data[j+1]; data[j+1]=temp; flag=1; } } if (flag==0){ break; } } return data; }
插入排序
-
思路:类似于斗地主时候整理手牌,一步一步把最小的牌放在最左边,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
-
性能分析:
最佳情况:T(n) = O(n)
最坏情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
空间复杂度:O(1)
稳定性:稳定(相同元素的相对位置不会改变) -
适用:适用于数量较少的排序。
-
特点:同为稳定性排序算法,时间复杂度也一样,插入排序的效率比冒泡排序要好,尤其是数据量大的时候,冒泡排序移动数据的操作更多,只要是小于后一个元素,就要再遍历一次。所以它的效率低。(一般情况)
-
图解
-
实现
//由于是从第一个元素开始比较,可以知道current前面都是有序递增的,注意后移要从最后开始 int [] insertSort(int[] target){ int temp; int current=0; for (int i = 1; i <target.length ; i++) { temp=target[i]; current=i-1; while(current>=0&&temp<target[current]){ target[current+1]=target[current]; current--; } target[current+1]=temp; } return target; }
选择排序
-
思路:在选择排序中,不再只比较两个相邻的数据。因此需要记录下某一个数据的下标,进行选择排序就是把所有的数据扫描一遍,从中挑出(按从小到大排序)最小的一个数据,这个最小的数据和最左端下标为0的数据交换位置。之后再次扫描数据,从下标为1开始,还是挑出最小的然后和1号位置进行交换,这个过程一直持续到所有的数据都排定。而程序中需要有一个标识变量来标识每次挑出最小数据的下标。
-
特点:选择排序改进了冒泡排序,将必要的交换次数从 O(N2)减少到 O(N)次。不幸的是比较次数仍然保持为 O(N2)。然而,选择排序仍然为大记录量的排序提出了一个非常重要的改进,因为这些大量的记录需要在内存中移动,这就使交换的时间和比较的时间相比起来,交换的时间更为重要。(一般来说,在 Java 语言中不是这种情况,Java 中只是改变了引用位置,而实际对象的位置并没有发生改变。)
-
性能分析:
2.4 算法分析
最佳情况:T(n) = O(n2)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
稳定性:不稳定。 -
图解
- 实现:
int [] selectSort(int [] target){ int current; int temp; for (int i = 0; i <target.length ; i++) { current=i; for (int j = i; j <target.length ; j++) { if(target[current]>target[j]){ current=j; } } temp=target[current]; target[current]=target[i]; target[i]=temp; } return target; }
参考文章:https://blog.csdn.net/u013249965/article/details/52575324
4. 三种基础算法的比较
除非手边没有算法可以参考,一般情况几乎不太使用冒泡排序算法。它过于简单了,以至于可以毫不费力地写出来。然而当数据量很小的时候它会有些应用的价值。
选择排序虽然把交换次数降到了最低,但比较的次数仍然很大。当数据量很小,并且交换数据相对于比较数据更加耗时的情况下,可以应用选择排序。
在大多情况下,假设当数据量比较小或者基本上有序时,插入排序算法是三种简单排序算法中最好的选择。对于更大数据量的排序来说,快速排序通常是最快的方法:之后会有介绍。
参考文章:https://blog.csdn.net/morewindows/article/details/6684558
5. 快速排序
- 思路:该方法的基本思想是:
-
先从数列中取出一个数作为基准数。
-
分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
-
再对左右区间重复第二步,直到各区间只有一个数。
-
性能:
最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(nlogn)
稳定性:不稳定 -
特点:采用分治法,速度很快,但是是不稳定,不适合对象排序,我这里采用的是第一个数字作为基准数,实际情况尽量不用这样适用。
-
图解
-
实现
void quickSort(int[] target,int min,int max){ if(max>min){ int i=min,j=max; int x=target[min]; //保存基准数后,找到最后的位置,将参照数放到指定位置上就行。 while(i<j){ while (i<j&&target[j]>=x){ j--; } if (i<j) target[i]=target[j]; while(i<j&&target[i]<=x){ i++; } if(i<j){ target[j]=target[i]; } } target[i]=x; quickSort(target,i+1,max); quickSort(target,min,i-1); } }
参考文章:https://blog.csdn.net/MoreWindows/article/details/6678165#commentBox
归并排序
-
思路:和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
-
图解
-
特点: 也是分治法的思想,速度较快,次于快速排序, 缺点是辅存很大,但是是稳定的排序算法,适合对象排序。
-
性能:
最佳情况:T(n) = O(n)
最差情况:T(n) = O(nlogn)
平均情况:T(n) = O(nlogn)
稳定性:稳定
. -
实现
void mergeSort(int [] target){ int [] temp=new int[target.length]; mergeSort(target,0,target.length-1,temp); } void mergeSort(int []target, int left,int right,int[] temp){ if(left<right){ int mid=left+(right-left)/2; mergeSort(target,left,mid,temp); mergeSort(target,mid+1,right,temp); mergeSort(target,left,mid,right,temp); } } //合并两个数组的操作 void mergeSort(int []target, int left,int mid,int right,int[] temp){ int k=0; //两个数组的第一个值 int left_frist=left; int right_frist=mid+1; while (left_frist<=mid&&right_frist<=right){ if(target[left_frist]>target[right_frist]){ temp[k++]=target[right_frist++]; }else { temp[k++]=target[left_frist++]; } } //一个合并好,另外一个还没有 while (left_frist<=mid){ temp[k++]=target[left_frist++]; } while (right_frist<right){ temp[k++]=target[right_frist++]; } //最后改变target数组,记得是改变left开头的 for (int i = 0; i < k; i++) target[left+ i] = temp[i]; }
- 归并相关算法题:
1)求数组逆序对,前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
-
例如:数组1,2,3,4,5,6,7,0有(1,0);(2.0)。。。七个逆序对。
-
思路,归并排序时,在合并的时候前后两部分都是已经排序好的,我们把则start到mid和mid+1到end中,如果后半部分的其中一个数小于前面的,则小于前面数组start_frist到mid+1的所有数,所以count+=mid+1-start-frist。
public class Solution { int count=0; public int InversePairs(int [] array) { int [] temp=new int[array.length]; getCount(array,0,array.length-1,temp); return count%1000000007; } void getCount(int [] array,int start,int end,int [] temp){ if(start<end){ int mid=(end-start)/2+start; getCount(array,start,mid,temp); getCount(array,mid+1,end,temp); getCount(array,start,mid,end,temp); } } void getCount(int [] array, int start, int mid,int end,int [] temp){ int k=0; int start_frist=start; int end_frist=mid+1; while(start_frist<=mid&&end_frist<=end){ if(array[start_frist]<array[end_frist]){ temp[k++]=array[start_frist++]; } else{ if(count>=1000000007)//数值过大求余 { count%=1000000007; } //一旦小于,则代表start_Frist到mid部分都大于end_frist这个值,所以全部要加上去,再加上我的前部分是包括mid,所以要先mid+1再减区start_frist count+=(mid+1-start_frist); temp[k++]=array[end_frist++]; } } while(start_frist<=mid){ temp[k++]=array[start_frist++]; } while(end_frist<=end){ temp[k++]=array[end_frist++]; } for(int i=0;i<k;i++){ array[i+start]=temp[i]; } } }
以后持续更新
- .Net,Java与C/C++语言的比较(配合代码实现,继续更新中......)
- 详解直接插入排序算法与相关的Java版代码实现
- 十一种排序算法整理(JAVA实现,持续更新中)
- 常见算法的java实现代码(持续更新中)
- java排序算法实现代码
- 常见排序算法的C语言实现以及算法复杂度分析(持续更新)
- JAVA实现各种排序算法----更新中----
- android 横屏竖屏设置的详解(最好不要用Java代码实现,用xml配置文件吧)
- Java实现几种常见排序算法代码
- 算法 -- Java实现选择排序(图解 + 代码实现)
- 一遍记住Java常用的八种排序算法与代码实现
- 堆排序原理详解和Java实现代码
- java实现各种排序算法及比较
- 各种排序算法的分析及其Java代码的实现
- 算法#12--详解各种字符串排序算法和代码实现
- 算法 -- Java实现快速排序(图解 + 代码实现)
- java 算法之希尔排序详解及实现代码
- 各种排序算法的总结、比较与Java实现
- 一遍记住Java常用的八种排序算法与代码实现
- 平衡二叉搜索树(AVL树)的原理及实现源代码(有图文详解和C++、Java实现代码)