常用排序算法总结(java实现)

本文深入讲解了冒泡排序、选择排序、插入排序等经典排序算法的实现原理与代码实现,对比了各种排序算法的优缺点及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

      最近在碰到排序问题时,总是会一知半解,或者想不起来某些排序算法的原理。况且排序算法在面试中也占据了一定的分量,且是很多算法的基础。因此还是结合网上已有的算法总结,来写一篇自己的排序算法总结。

排序的定义:

      排序,顾名思义就是对一组数据,或者是可比较的一些对象(用java的思想,一切事物皆对象)从小到大或者从大到小进行顺序的排序(一般为从小到大)。

常见术语:

      稳定:如果两个数或者对象相同,假设为a.b(即比较的属性相同时),如果未排序前a在b前面,则排序后a一定在b前面。这种排序视为稳定的。一个思考是为什么要提出排序稳定的概念,在哪里会得到应用吗?
      不稳定:与稳定相对。a可能会出现在b的后面。

      内排序:所有的操作都在内存中完成。
      外排序:一般是由于数据太大,在内存中无法放下所有的数据,需要借助磁盘来完成排序。

      时间复杂度:算法执行所耗费的时间(可以这样解释,但是不够准确)
      空间复杂度:运行程序所需内存的大小。

常见排序算法比较

常见排序算法分类:
       常见排序算法分类
                  图1:常见排序算法分类(引自参考文章1)

冒泡排序(Bubble Sort)

       冒泡排序,在生活中,水中的泡泡从水底到水面的过程中会由于压强的原因,泡泡逐渐变大,借鉴这个思想,一次从水底到水面的冒泡,将会使得最大的值到水面,因此下次比较就可以只比较水面下的一层即可。

具体算法描述:
       <1>.先比较前两个数,如果1大于2,则进行交换,
       <2>重复1的过程直到末尾,则完成一次冒泡,则最后一个数为最大值
       <3>重复1 2的过程直到结束(不包括最后一个数)

代码如下:

public void sort(int[] arr){
    int len = arr.length;
    if(len<=1)
        return ;
    for(int i =len;i>0;i--){
        for(int j =1;j<i;j++){
            if(arr[j-1]>arr[j]){
                swap(arr,j-1,j);
            }
        }
    }
}

public void swap(int[] arr, int i, int j) {
    // TODO Auto-generated method stub
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
@Test
public void sortTest(){ //测试
    int[] arr = {2,3,1,4,2,0,5,-1,2};
    sort(arr);
    for(int i =0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
}

算法分析:
       1.是否稳定?
              稳定
       2.内外排序?
              内排序
       3.复杂度分析
              最佳情况:
              输入数据为正序,复杂度为T(n)= O(n)
              最差情况:
              输入数据为反序,复杂度为T(n)= O(n2)
              平均情况:T(n)= O(n2)
在参考文章1中,存在两种简单的改进方法,有兴趣的可以参考一下。

选择排序(Selection Sort)

选择排序:
       每次在未排序的序列中选择出最小的值或者最大值,将选择出来的值放在队首或者队末

具体算法描述:
       <1>先比较前两个数,如果a>b.则将其下标。
       <2>重复1的过程直到末尾,然后将当前最小值放到第i次的位置
       <3>重复1 2的过程直到结束(不包括最后一个数)
代码如下:

public void selectSort(int[] arr){
    int len = arr.length;
    if(len<=1){
        return ;
    }
    int minIndex=0;
    for(int i =0;i<len-1;i++){
        minIndex=i;
        for(int j=i+1;j<len;j++){
            if(arr[j]<arr[minIndex]){
                minIndex=j;
            }
        }
        swap(arr,i,minIndex);
    }
}
@Test
public void selectSortTest(){//测试
    int[] arr = {2,3,1,4,2,0,5,-1,2};
    selectSort(arr);
    for(int i =0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
}

算法分析:
     1.是否稳定?
          稳定
     2.内外排序?
          内排序
     3.复杂度分析
          最佳情况:
          最差情况:
          平均情况:
          全是T(n) = O(n2),

插入排序(Insertion Sort)

插入排序:
     将队列分为有序队列和无序队列,从无序队列取出一个值,插入有序队列,直到无序队列中没有元素.

具体算法描述:
     <1>第一个元素可以被当做是有序元素
     <2>取下一个元素,在已经排序的元素队列中从后往前扫描,
     <3>如果该元素大于这个取出的元素,则该元素后移一位
     <4>直到当前元素等于或者小于取出的元素,则放在当前位置+1的位置
     <5>重复2-3-4的过程

代码如下:

public void insertSort(int[] arr){
    int len = arr.length;
    if(len<=1)
        return ;
    for(int i =1;i<len;i++){
        int temp =arr[i];
        int j =i-1;
        while(j>=0&&arr[j]>temp){
            arr[j+1]=arr[j];
            j--;
        }
        arr[j+1]=temp;
    }
} 
@Test
public void insertSortTest(){
    int[] arr = {2,3,1,4,2,0,5,-1,2,8,7};
    insertSort(arr);
    for(int i =0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
}

算法分析:
      1.是否稳定?
            稳定
      2.内外排序?
            内排序
      3.复杂度分析
            最佳情况:
            正序时:T(n) = O(n)
            最差情况:
            反序时:T(n) = O(n2)
            平均情况:
            T(n) = O(n2)

      改进方案,在每次查找插入点的时候,由于之前的序列是属于有序序列,因此可以通过2分插入来提高速度。

希尔排序(Shell Sort)

希尔排序:
      希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。

增量序列:
      在这里选择增量序列gap = length/2;然后gap会继续与gap/2的方式进行递减,将{n/2,n/2/2,…,1}称为增量序列(注:增量序列的选取不限于这一种取法,如果合理的选取增量序列也是值得讨论的)。假设当前增量为5时,小标为0将与5,10,等等的序列进行插入排序。

具体算法描述:
      <1>首先选择一个增量序列,
      <2>根据增量序列进行k趟排序
      <3>改变增量序列,重复1-2过程,直到增量等于0停止
代码如下:

public void shellSort(int[] arr){
    int len = arr.length;
    if(len<=1)
        return ;
    for(int gap = len/2;gap>0;gap/=2){
        for(int i = gap;i<len;i++){
            int temp = arr[i];
            int j =i;
            while(j>=gap&&arr[j-gap]>temp){
                arr[j] = arr[j-gap];
                j-=gap;; 
            }
            arr[j] = temp;
        }
    }
} 
@Test
public void shellSortTest(){
    int[] arr = {2,3,1,4,2,0,5,-1,2,8,7};
    shellSort(arr);
    for(int i =0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
}

算法分析:
    1.是否稳定?
        不稳定
    2.内外排序?
        内排序
    3.复杂度分析
        经过优化后的增量序列可以在最坏情况下达到O(n3/2)的复杂度。

归并排序(Merge Sort)

归并排序:
       归并排序是建立在归并操作上的简单排序。采用分治的思想,归并排序是一种稳定的排序方法。
将有序的子序列进行合并,得到完全有序的序列。

具体算法描述:
     <1>将长度为n的序列分成长度为n/2的子序列,直到子序列的长度为1或者0时。
     <2>再将两个子序列进行归并排序。
     <3>重复执行2得到最终的有序序列。

代码如下:

public void mergeSort(int[] arr){
        int len = arr.length;
        int[] tempArr = new int[len];
        mergeSort(arr,tempArr,0,len-1);
    }
    private void mergeSort(int[] arr,int[] tempArr,int start,int end){
        if(start<end){
            int center = (start+end)/2;
            mergeSort(arr,tempArr,start,center);
            mergeSort(arr,tempArr,center+1,end);
            merge(arr,tempArr,start,center,end);
        }
    }
    private void merge(int[] arr,int[] tempArr,int start,int leftEnd, int end){
        int num = end - start +1;
        int left = start;
        int right_start = leftEnd+1;
        while(left<=leftEnd&&right_start<=end){
            if(arr[left]<=arr[right_start]){
                tempArr[start++] = arr[left++];
            }else{
                tempArr[start++] = arr[right_start++];
            }
        }
        while(left<=leftEnd){
            tempArr[start++] = arr[left++]; 
        }
        while(right_start<=end){
            tempArr[start++] = arr[right_start++];
        }
        for(int i=0;i<num;i++,end--){
            arr[end] = tempArr[end];
        }
    }
    @Test
    public void mergeSortTest(){//测试
        int[] arr = {2,3,1,4,2,0,5,-1,2,8,7};
        mergeSort(arr);
        for(int i =0;i<arr.length;i++){
            System.out.print(arr[i]+",");
        }
    }

算法分析:
      1.是否稳定?
            稳定
      2.内外排序?
            内排序
      3.复杂度分析
            最佳情况:
            T(n) = O(n);
            最差情况:
            T(n) = O(nlogn);
            平均情况:
            T(n) = O(nlogn);

快速排序(Quick Sort)

快速排序:

      快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一次排序将序列分割成两部分,其中一部分给另外一个部分的数据都要小,然后再按照这个方法,处理分割的两部分,从而使得整个序列变得有序。

具体算法描述:
      <1>首先选取一个基准p,将小于p的部分放在左边,大于p的部分放在右边,(注:基准选为子数组的第一个元素较为常见)
      <2>然后再对余下的两部分分别进行1中的部分,当子序列长度为1时,则直接返回
      <3>最终会得到有序的序列
代码如下:

public void quickSort(int[] arr){
    if(arr==null)
        return;
    int len = arr.length;
    if(len<=1)
        return ;
    quickSortM(arr,0,len-1);//注意为长度减1,因为包含左包含右
}
public void quickSortM(int[] arr,int startIndex,int endIndex){//包含左包含右
    if(startIndex>=endIndex)
        return ;
    int boundary = divide(arr,startIndex,endIndex);
    quickSortM(arr,startIndex,boundary-1);
    quickSortM(arr,boundary+1,endIndex);
}
public int divide(int[] arr,int startIndex,int endIndex){
    int standard = arr[startIndex];
    int leftIndex = startIndex;
    int rightIndex= endIndex;
    while(leftIndex<rightIndex){
        while(leftIndex<rightIndex&&arr[rightIndex]>=standard){
            rightIndex--;
        }
        arr[leftIndex] = arr[rightIndex];
        while(leftIndex<rightIndex&&arr[leftIndex]<=standard){
            leftIndex++;
        }
        arr[rightIndex] = arr[leftIndex];
    }
    arr[leftIndex] = standard;
    return leftIndex;
}
@Test
public void quickSortTest(){
    int[] arr = {2,3,1,4,2,0,5,-1,2,8,7};
    quickSort(arr);
    for(int i =0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
}

算法分析:
      1.是否稳定?
            不稳定
      2.内外排序?
            内排序
      3.复杂度分析
            最佳情况:
            T(n) = O(nlogn)
      分析:假如每次取到的基准都是中位数,那么左右子序列的数量就是相同的,因此迭代的深度为log2n,每次的计算量就是n,因此算法时间复杂度为 O(nlogn)
      最差情况:
      每次取得的基准都是最小的数,或者最大的数,则每次取值使得序列长度减1,复杂度为O(n2)
      平均情况为O(nlogn)
注:此部分参考文章3.

堆排序(Heap Sort)

堆排序:
      堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。

堆:
      堆是一种完全二叉树,并且具有以下性质:左节点和右节点的值都小于父节点的叫最大堆,左右节点都大于父节点的叫最小堆。

堆排序的基本思想:
      首先将待排序列构造成一个最大堆,然后使用最大堆根节点与n-i的元素(i为第i次重复),然后调整使其成为一个最大堆。重复n次上述过程。

具体算法描述:
      <1>首先构造一个最大堆。
      <2>最大堆的根节点和n-i的元素进行交换
      <3>然后再调整使其成为一个最大堆
      <4>重复2 3过程n次

接下来介绍完全二叉树的性质:
介绍完全二叉树的性质是为了编程易于理解,注:这里的序列的下标是以1而不是0开始的。
      1:如果当前节点为i,则父节点为floor(i/2);
      2:如果2i<=n,则存在左孩子,且左孩子为2i;
      3:如果2i>n,则无左孩子;
      4: 如果2i+1<=n;则存在右孩子,为2i+1;
      5:如果2i+1>n,则节点i无右孩子;

注:如果使用数组来考虑的,下标是从0开始的,因此性质有所改变
1:如果当前节点为i,父节点为(i-1)/2;
2:如果2i+1< n,则存在左孩子,且左孩子为2i+1;
3:如果2i+2< n,则存在右孩子,且右孩子为2i+2;

代码如下:

public void heapSort(int[] arr){
    //构建最大堆的过程
    for(int i = arr.length/2-1;i>=0;i--){
        adjustHeap(arr,i,arr.length);
    }
    for(int j=arr.length-1;j>0;j--){
        swap(arr,0,j);
        adjustHeap(arr,0,j);
    }
}
public void adjustHeap(int[] arr,int i,int len){
    int temp = arr[i];
    for(int k=2*i+1;k<len;k=k*2+1){
        if(k+1<len&&arr[k]<arr[k+1]){
            k++;
        }
        if(temp<arr[k]){
            swap(arr,i,k);
            i=k;
        }else{
            break;
        }
    }
    //arr[i] = temp;
}
@Test 
public void heapSortTest(){//测试
    int[] arr = {2,3,1,4,2,0,5,-1,2};
    heapSort(arr);
    for(int i =0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
}

算法分析:
     1.是否稳定?
          不稳定
     2.内外排序?
          内排序
     3.复杂度分析
          最佳情况:
          T(n) = O(nlogn)
          最差情况:
          T(n) = O(nlogn)
          平均情况为T(n) = O(nlogn)
     堆排序分为两部分,第一部分为建立最大堆的过程经推导为O(n)复杂度(值得推敲),第二部分需要交换n-1次,根据二叉树的性质每次最多log2n的复杂度,因此总的复杂度为O(nlogn)的复杂度
     注:此部分参考文章4.
参考文章:

1,https://www.cnblogs.com/jztan/p/5878630.html
2,https://www.cnblogs.com/chengxiao/p/6104371.html
3,http://blog.youkuaiyun.com/lemon_tree12138/article/details/50622744
4,https://www.cnblogs.com/chengxiao/p/6129630.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值