排序算法

Posted zxcoder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法相关的知识,希望对你有一定的参考价值。

10种排序算法的Java实现

import java.util.*;

public class Sort {
    static int min(int a,int b){
        return Math.min(a,b);
    }
    static int max(int a,int b) { return Math.max(a,b); }
    static void swap(int[] a,int i,int j){
        int t=a[i];
        a[i]=a[j];
        a[j]=t;
    }
    static void print(int[] a){
        for(int v:a){
            System.out.print(v+" ");
        }
        System.out.println();
    }

    /**
     * 冒泡排序,比较相邻两个元素并交换,每扫一遍将最大的元素交换到最后
     * 平均O(N^2) 最好O(N) 最坏O(N^2) 空间O(1) 稳定(相同元素不会交换)
     * @param a
     */
    static void bubbleSort(int[] a){
        int n=a.length;
        for(int i=n;i>0;i--){
            boolean flag=false;
            for(int j=1;j<i;j++){
                if(a[j-1]>a[j]){
                    flag=true;
                    swap(a,j-1,j);
                }
            }
            //将最好时间优化到O(N)
            if(!flag){
                break;
            }
        }
    }

    /**
     * 选择排序,每次选择剩下的最小元素放在正确位置
     * 平均O(N^2) 最好O(N^2) 最坏O(N^2) 空间O(1) 不稳定(比如[5 8 5 2 9],第一个5会跟2交换,改变5的相对位置)
     * @param a
     */
    static void selectSort(int[] a){
        int n=a.length;
        for(int i=0;i<n-1;i++){
            int mn=Integer.MAX_VALUE;
            int idx=i;
            for(int j=i;j<n;j++){
                if(a[j]<mn){
                    mn=a[j];
                    idx=j;
                }
            }
            swap(a,i,idx);
        }
    }

    /**
     * 插入排序,前面部分是有序数组,把后面部分一个个插入到正确位置
     * 平均O(N^2) 最好O(N) 最坏O(N^2) 空间O(1) 稳定(相同值不会插入)
     * @param a
     */
    static void insertSort(int[] a){
        int n=a.length;
        for(int i=1;i<n;i++){
            if(a[i]>a[i-1]){
                //将最好时间优化到O(N)
                continue;
            }
            int t=a[i];
            int j=i-1;
            for(;j>=0;j--){
                if(t<a[j]){
                    a[j+1]=a[j];
                }else{
                    break;
                }
            }
            a[j+1]=t;
        }
    }

    /**
     * 希尔排序(缩小增量插入排序)
     * 平均O(N^(1.3~2)) 最好O(N) 最坏O(N^2) 空间O(1) 不稳定(同一个值分在不同的组)
     * @param a
     */
    static void shellSort(int[] a){
        int n=a.length;
        //h为间隔/组数
        for(int h=n/2;h>0;h/=2){
            for(int i=h;i<n;i++){
                //间隔为h的插入排序,普通插入排序就是间隔为1
                if(a[i]>a[i-h]){
                    continue;
                }
                int t=a[i];
                int j=i-h;
                for(;j>0;j-=h){
                    if(t<a[j]){
                        a[j+h]=a[j];
                    }else{
                        break;
                    }
                }
                a[j+h]=t;
            }
        }
    }

    private static void mergeSort(int[] a,int l,int r){
        int len=(r-l+1);
        //剩一个元素,递归边界
        if(len<2){
            return;
        }
        int mid=(l+r)/2;
        //分
        mergeSort(a,l,mid);
        mergeSort(a,mid+1,r);
        //合并
        int[] b=new int[len];
        for(int idx=0,i=l,j=mid+1;idx<len;idx++){
            if(i==mid+1){
                b[idx]=a[j++];
            }else if(j==r+1){
                b[idx]=a[i++];
            }else if(a[i]<a[j]){
                b[idx]=a[i++];
            }else{
                b[idx]=a[j++];
            }
        }
        System.arraycopy(b,0,a,l,len);
    }

    /**
     * 归并排序,分治法,将两个有序的序列合并
     * 平均O(NlogN) 最好O(NlogN) 最坏O(NlogN) 空间O(N) 稳定
     * 空间算的是最大,所以是O(N)+O(N/2)+O(N/4)+...=O(N)
     * @param a
     */
    static void mergeSort(int[] a){
        int n=a.length;
        mergeSort(a,0,n-1);
    }

    //快排的排序其实是在划分这里
    private static int partition(int[] a,int l,int r){
        int t=a[l];
        while(l<r){
            while(l<r && a[r]>=t){
                r--;
            }
            a[l]=a[r];
            while(l<r && a[l]<t){
                l++;
            }
            a[r]=a[l];
        }
        a[l]=t;
        return l;
    }

    private static void quickSort(int[] a,int l,int r){
        int len=(r-l+1);
        if(len<2){
            return;
        }
        int p=partition(a,l,r);
        quickSort(a,l,p);
        quickSort(a,p+1,r);
    }

    /**
     * 快速排序,选定中轴进行划分,在递归划分的过程中进行排序
     * 平均O(NlogN) 最好O(NlogN) 最坏O(N^2) 空间O(logN) 空间最坏O(N) 不稳定(双指针扫的时候会改变相同数的相对位置)
     * @param a
     */
    static void quickSort(int[] a){
        int n=a.length;
        quickSort(a,0,n-1);
    }

    /**
     * 将数组a中,根为节点i的子树大顶堆化
     * @param a
     * @param i
     */
    private static void heapify(int[] a,int i,int n){
        int ls=2*i+1;
        int rs=2*i+2;
        int mx=i;
        if(ls<n && a[ls]>a[mx]){
            mx=ls;
        }
        if(rs<n && a[rs]>a[mx]){
            mx=rs;
        }
        if(mx!=i){
            swap(a,i,mx);
            heapify(a,mx,n);
        }
    }

    /**
     * 根据数组a前n个数建立大顶堆
     * @param a
     */
    private static void buildMaxHeap(int[] a,int n){
        //从最后一个非叶子节点开始,自底向上
        //最后一个叶子是n-1,最后一个非叶子就是n-1的父节点
        for(int i=(n-1)/2-1;i>=0;i--){
            heapify(a,i,n);
        }
    }

    /**
     * 堆排序,根据数组建立堆(数组模拟),每次取出堆顶放在最后,调整剩下的节点重新成为堆
     * 平均O(NlogN) 最好O(NlogN) 最坏O(NlogN) 空间O(1) 不稳定(每次把堆顶放在最后,有跳跃交换)
     * @param a
     */
    static void heapSort(int[] a){
        int n=a.length;
        buildMaxHeap(a,n);
        for(int i=n-1;i>=0;i--){
            swap(a,0,i);
            n--;
            heapify(a,0,n);
        }
    }

    /**
     * 计数排序,适合数据范围较小
     * 平均O(n+k) 最好O(n+k) 最坏O(n+k) 空间O(k) 稳定(前缀和优化后属于稳定排序)
     * @param a
     */
    static void countSort(int[] a){
        int n=a.length;
        int mn=Integer.MAX_VALUE;
        int mx=Integer.MIN_VALUE;
        for(int v:a){
            mn=min(mn,v);
            mx=max(mx,v);
        }
        int len=mx-mn+1;
        int[] cnt=new int[len];
        Arrays.fill(cnt,0);
        //计数
        for(int v:a){
            cnt[v-mn]++;
        }
        //前缀和
        for(int i=1;i<len;i++){
            cnt[i]+=cnt[i-1];
        }
        int[] b=new int[n];
        //倒序遍历原数组,保证后面的元素排名靠后
        for(int i=n-1;i>=0;i--){
            b[cnt[a[i]-mn]-1]=a[i];
            cnt[a[i]-mn]--;
        }
        System.arraycopy(a,0,b,0,n);
        //不加优化版,不稳定
        // int idx=0;
        // for(int i=0;i<len;i++){
            // while(cnt[i]-->0){
                // a[idx++]=i+mn;
            // }
        // }
    }

    static ArrayList<Integer> bucketSort(ArrayList<Integer> a, int bucketSize){
        int n=a.size();
        int mn=Integer.MAX_VALUE;
        int mx=Integer.MIN_VALUE;
        for(int v:a){
            mn=min(mn,v);
            mx=max(mx,v);
        }
        int bucketCount=(mx-mn)/bucketSize+1;
        ArrayList<ArrayList<Integer>> bucket=new ArrayList<>(bucketCount);
        ArrayList<Integer> ans=new ArrayList<>();
        for(int i=0;i<bucketCount;i++){
            bucket.add(new ArrayList<>());
        }
        //装桶
        for(int v:a){
            bucket.get((v-mn)/bucketSize).add(v);
        }
        for(int i=0;i<bucketCount;i++){
            //每个桶单独排序再合并,可以调用其他排序算法
            Collections.sort(bucket.get(i));
            ans.addAll(bucket.get(i));
        }
        return ans;
    }

    /**
     * 桶排序(升级版计数排序)
     * 平均O(N+k) 最好O(N) 最坏O(N^2) 空间O(N+k) 稳定
     * @param a
     */
    static void bucketSort(int[] a){
        int n=a.length;
        int bucketSize=6;
        ArrayList<Integer> al=new ArrayList<>();
        for(int v:a){
            al.add(v);
        }
        ArrayList<Integer> ar=bucketSort(al,bucketSize);
        for(int i=0;i<n;i++){
            a[i]=ar.get(i);
        }
    }

    /**
     * 基数排序,按位,每一位分别进行计数排序
     * 平均O(N*k) 最好O(N*k) 最坏O(N*k) 稳定
     * @param a
     */
    static void radixSort(int[] a){
        int n=a.length;
        int mx=0;
        for(int v:a){
            mx=max(mx,v);
        }
        int dig=0;
        while(mx>0){
            dig++;
            mx/=10;
        }
        ArrayList<ArrayList<Integer>> al=new ArrayList<>();
        for(int i=0;i<10;i++){
            al.add(new ArrayList<>());
        }
        for(int i=0,mod=10,div=1;i<dig;i++,mod*=10,div*=10){
            //每一位
            for(int v:a){
                int x=(v%mod)/div;
                al.get(x).add(v);
            }
            int idx=0;
            for(int j=0;j<10;j++){
                int siz=al.get(j).size();
                for(int k=0;k<siz;k++){
                    a[idx++]=al.get(j).get(k);
                }
                al.get(j).clear();
            }
        }
    }

    public static void main(String[] args) {
        int[] a={1,8,6,7,15,11,4,2,13,14,12,10,5,9,3,14,8,8,6,11};
//        bubbleSort(a);
//        selectSort(a);
//        insertSort(a);
//        shellSort(a);
//        mergeSort(a);
//        quickSort(a);
//        heapSort(a);
//        countSort(a);
//        bucketSort(a);
//        radixSort(a);
        print(a);
    }
}

分类

  • 基于比较的排序算法
    冒泡排序,选择排序,插入排序,希尔排序,归并排序,快速排序,堆排序
  • 非比较的排序算法
    计数排序,桶排序,基数排序
  • 稳定的排序算法
    冒泡排序,插入排序,归并排序,计数排序,桶排序,计数排序
  • 不稳定的排序算法
    选择排序,希尔排序,快速排序,堆排序

快排和归并的空间复杂度对比

快排平均是O(logN) 最坏是O(N),而归并空间是O(N)。
两者的区别在于快排是先partition,然后再递归下去,每层递归都会需要partition的临时空间O(1),加起来就是O(logN),如果退化到冒泡,就是O(N)。
而归并是先两个sort递归下去,然后再merge,每层递归用的临时空间都是一样的O(N)级别。

以上是关于排序算法的主要内容,如果未能解决你的问题,请参考以下文章

算法排序之堆排序

快速排序-递归实现

从搜索文档中查找最小片段的算法?

在第6731次释放指针后双重免费或损坏

TimSort算法分析

以下代码片段的算法复杂度