必备排序算法详解(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. 比较排序:
  • 常见:快速排序,归并排序,堆排序,冒泡排序等在排序的过程中,元素之间的次序依赖于元素之间的比较,每个数都必须与其他数进行比较的排序算法。
  • 特点:适用于各种规模的数据,不在乎数据的分布,比较万金油
  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. 适用:适用于数量较少的排序。

    扫描二维码关注公众号,回复: 9972007 查看本文章
  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--;
               }
               target[i]=target[j];
               while(i<j&&target[i]<=x){
                   i++;
               }
               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];
        }
    }
    
      
}

堆排序

参考文章:https://www.cnblogs.com/Java3y/p/8639937.html

  1. 定义: 利用堆这种数据结构所设计的一种排序算法,堆积是一个近似于完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于它的父节点。

  2. 大概思路:
    1)将初始待排序关键字序列构建为大顶堆,此堆为初始的无序区;
    2)将堆顶元素与最后一个元素交换,此时得到新的无序区R(1…n-1)和新的有序区R(n),且满足R[1,2,…n-1]<=R[n]
    3)由于交换后新的堆顶R(1)可能违反堆的性质,因此需要对当前无序区R(1…n-1)调整为新的堆,

  3. 图片
    在这里插入图片描述

  4. 代码实现

    //开始建堆,使当前堆的最大值在堆顶
    static void heapsort(int [] target ){
        int size=target.length-1;
        //第一次建堆,使整个数组为一个未排序的堆
        fristheap(target);
        size--;

        for (int i = 0; i <target.length-1 ; i++) {
            swap(target,0,size+1);
            heapify(target,0,size);
            size--;
        }

    }

    //第一次建堆,找出最大值,并使整个树作为一个标准的大顶堆,但是未排序
    static void fristheap(int [] target ){
        //首先从最右子树非叶子节点开始,减1是因为下标从0开始
        int flag=target.length/2-1;
        //记得是直到0的
        for (int i=flag; i >=0 ; i--) {
            heapify(target,i,target.length-1);
        }
    }

    //对以该节点为根的树进行建堆,使最大的max在最上面,
    // 因为我们是从下到上排序子树的,所以不需要考虑子树的子树是否还未排序
    static void heapify(int [] target,int curr,int size){

        //如果当前节点大于数组长度,则没有子节点,该节点必然是最大值
        if(curr<size){
            //注意下标是从0开始的,记得多加一个1
            int left=curr*2+1;
            int right=curr*2+2;
            //首先假设curr是最大值
            int max=curr;
            if(left<size){
                if(target[left]>target[max]){
                    max=left;
                }
            }
            if (right<size){
                if(target[right]>target[max]){
                    max=right;
                }
            }
            if(max!=curr){
                //如果不同则交换值
                swap(target,max,curr);
                //因为max节点值变了,所以以该节点为根的树也同时改变
                heapify(target,max,size);
            }
        }
    }

    static void swap(int [] target,int i,int j){
        int temp=target[i];
        target[i]=target[j];
        target[j]=temp;
    }
发布了36 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/s_xchenzejian/article/details/98077573