您的位置:首页 > 编程语言 > Java开发

必备排序算法详解(java代码实现,图解,比较等,持续更新中)

2019-08-06 22:11 330 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/s_xchenzejian/article/details/98077573

参考文章: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. 比较排序:
  • 常见:快速排序,归并排序,堆排序,冒泡排序等在排序的过程中,元素之间的次序依赖于元素之间的比较,每个数都必须与其他数进行比较的排序算法。
  • 特点:适用于各种规模的数据,不在乎数据的分布,比较万金油
  1. 非比较排序:
  • 常见:计数排序,基数排序,桶拍序等通过确定每个元素前的元素个数来进行排序的算法。
  • 特点:只有确定每个元素前面元素的个数,即可一次遍历解决,但是由于需要占用空间来确定唯一位置,所以对数据规模和数据分布有一定要求。

1. 冒泡排序

  1. 思路:
    第一步:从第一个元素开始,比较相邻元素后判断是否交换,直到最后一个元素。
    第二步:针对所有的元素重复第一步,直到遍历中没有出现交换则结束排序。

  2. 性能分析:
    时间复杂度:
    2.1. 最好情况:O(n)
    2.2. 平均情况:O(n^2)
    2.3. 最坏情况:O(n^2)
    空间复杂度:O(1)
    稳定性:稳定(相同元素的相对位置不会改变)

  3. 适用场景

  • 适用元素较少的情况下,元素太多的话,交换和比较次数都会很多,影响效率,元素多的情况适合用快速排序.
  • 当数组基本有序的情况下适合使用冒泡排序和直接插入排序,它们在基本有序的情况下排序的时间复杂度接近O(n).
  1. 图解:

  2. 代码实现

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;
}

插入排序

  1. 思路:类似于斗地主时候整理手牌,一步一步把最小的牌放在最左边,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

  2. 性能分析:
    最佳情况:T(n) = O(n)
    最坏情况:T(n) = O(n2)
    平均情况:T(n) = O(n2)
    空间复杂度:O(1)
    稳定性:稳定(相同元素的相对位置不会改变)

  3. 适用:适用于数量较少的排序。

  4. 特点:同为稳定性排序算法,时间复杂度也一样,插入排序的效率比冒泡排序要好,尤其是数据量大的时候,冒泡排序移动数据的操作更多,只要是小于后一个元素,就要再遍历一次。所以它的效率低。(一般情况)

  5. 图解

  6. 实现

//由于是从第一个元素开始比较,可以知道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;
}

选择排序

  1. 思路:在选择排序中,不再只比较两个相邻的数据。因此需要记录下某一个数据的下标,进行选择排序就是把所有的数据扫描一遍,从中挑出(按从小到大排序)最小的一个数据,这个最小的数据和最左端下标为0的数据交换位置。之后再次扫描数据,从下标为1开始,还是挑出最小的然后和1号位置进行交换,这个过程一直持续到所有的数据都排定。而程序中需要有一个标识变量来标识每次挑出最小数据的下标。

  2. 特点:选择排序改进了冒泡排序,将必要的交换次数从 O(N2)减少到 O(N)次。不幸的是比较次数仍然保持为 O(N2)。然而,选择排序仍然为大记录量的排序提出了一个非常重要的改进,因为这些大量的记录需要在内存中移动,这就使交换的时间和比较的时间相比起来,交换的时间更为重要。(一般来说,在 Java 语言中不是这种情况,Java 中只是改变了引用位置,而实际对象的位置并没有发生改变。)

  3. 性能分析:
    2.4 算法分析
    最佳情况:T(n) = O(n2)
    最差情况:T(n) = O(n2)
    平均情况:T(n) = O(n2)
    稳定性:不稳定。

  4. 图解

  1. 实现:
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. 快速排序

  1. 思路:该方法的基本思想是:
  • 先从数列中取出一个数作为基准数。

  • 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

  • 再对左右区间重复第二步,直到各区间只有一个数。

  1. 性能:
    最佳情况:T(n) = O(nlogn)
    最差情况:T(n) = O(n2)
    平均情况:T(n) = O(nlogn)
    稳定性:不稳定

  2. 特点:采用分治法,速度很快,但是是不稳定,不适合对象排序,我这里采用的是第一个数字作为基准数,实际情况尽量不用这样适用。

  3. 图解

  4. 实现

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

归并排序

  1. 思路:和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。

  2. 图解

  3. 特点: 也是分治法的思想,速度较快,次于快速排序, 缺点是辅存很大,但是是稳定的排序算法,适合对象排序。

  4. 性能:
    最佳情况:T(n) = O(n)
    最差情况:T(n) = O(nlogn)
    平均情况:T(n) = O(nlogn)
    稳定性:稳定
    .

  5. 实现

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)求数组逆序对,前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
  • 例如:数组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];
}
}

}

以后持续更新

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: