排序算法总结

主要参考Java 八大排序算法

排序算法总结

排序分内部排序和外部排序。

  • 外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。
  • 内部排序(常见8种)
排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性复杂性
直接插入排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定简单
希尔排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)不稳定较复杂
直接选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定简单
堆排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( 1 ) O(1) O(1)不稳定较复杂
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定简单
快速排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n 2 ) O(n^2) O(n2) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)不稳定较复杂
归并排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n ) O(n) O(n)稳定较复杂
基数排序 O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) O ( n + r ) O(n+r) O(n+r)稳定较复杂

在这里插入图片描述

插入排序

直接插入排序

private static void insertSort(int[] a){
        for (int i = 1; i < a.length; i++) {
            // 待插入元素
            int temp = a[i];
            int j;
            for (j = i - 1; j >= 0 && a[j] > temp; j--) {
                // 将大于temp的往后移动一位
                a[j + 1] = a[j];
            }
            a[j + 1] = temp;
        }
    }

二分插入排序

对直接插入排序的优化,寻找插入位置时,由于被插入部分是有序的,则可以二分查找。

private static void insertSortBin(int[] a){
        for (int i = 1; i < a.length; i++) {
            // 待插入元素
            int temp = a[i];
            int j;
            // 二分查找temp元素应插入的位置
            int start = 0;
            int end = i-1;
            int mid ;
            while (start<=end){
                mid = start+(end-start>>1);
                if (temp<=a[mid]){
                    end = mid-1;
                }else {
                    start = mid+1;
                }
            }
            for (j = i - 1; j >= start; j--) {
                // 将大于temp的往后移动一位
                a[j + 1] = a[j];
            }
            a[j + 1] = temp;
        }
    }

希尔排序

对以增量dt为间隔的数组,进行插入排序。(分组插入)
调整间隔,直到为1。

private static void shellSort(int[] a){
        int dk = a.length/2;
        while (dk>=1){
            shellInsertSortDk(a,dk);
            dk /= 4;
        }

    }
    private static void shellInsertSortDk(int[] a,int dk){
        for (int i = dk; i < a.length; i++) {
            // 待插入元素
            int temp = a[i];
            int j;
            // 从 i-dk 开始,到0结束,即在0~i之间,以dk为间隔
            for (j = i - dk; j >= 0 && a[j] > temp; j-=dk) {
                // 将大于temp的往后移动一位
                a[j + dk] = a[j];
            }
            a[j + dk] = temp;
        }
    }


选择排序

简单选择排序

/**
     * 简单选择排序:
     * 在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
     * @param a
     */
    public static void selectSort(int[] a){
        for (int i = 0; i < a.length; i++) {
            int min = Integer.MAX_VALUE;
            int ind = -1;
            for (int j = i; j < a.length; j++) {
                if (a[j]<min){
                    ind = j;
                    min = a[j];
                }
            }
            // 交换
            a[ind] = a[i];
            a[i] = min;
        }
    }

二元选择排序

/**
     * 二元选择排序,同时选择最大和最小,分别放到两侧
     * @param a
     */
    public static void selectDoubleSort(int[] a){
        int end = a.length-1;
        for (int i = 0; i <= end; i++,end--) {
            int min = Integer.MAX_VALUE;
            int max = Integer.MIN_VALUE;
            int indMin = -1;
            int indMax = -1;
            for (int j = i; j <= end; j++) {
                if (a[j]<min){
                    indMin = j;
                    min = a[j];
                }
                if (a[j]>max){
                    indMax = j;
                    max = a[j];
                }
            }
            // 交换
            a[indMin] = a[i];
            a[i] = min;
            // indMIN和i交换了,如果再用indMax和i或交换的话,就会错误
            if (indMax==i) indMax = indMin;
            else if (indMax==indMin) indMax = i;
            a[indMax] = a[end];
            a[end] = max;
        }
    }

堆排序

注意堆排序的一种错误写法(这种堆排序运行太慢了,比简单和二元花的时间还多)

 /**
     * 堆排序
     * @param a
     */
    public static void selectHeapSort(int[] a){
        // 把最大的放到末尾
        for (int i = 0, end = a.length-1; i <= end ; end--) {
            // 把最大的元素调到a[0]
            for (int j = (int)(end-0.5)/2 ; j >= 0 ; j--) {
                int temp = 2*j+1;
                if (2*j+2<=end && a[temp]<a[2*j+2]){
                    temp = 2*j+2;
                }
                if (2*j+1<=end && a[temp]>a[j]){
                    swap(a,temp,j);
                }
            }

            swap(a,end,0);
        }
    }
  • 问题就在于,每次选取最大元素放到末尾之后,要从最后一个节点(叶子节点)的父节点开始搜索,一直到序号为0的节点,也就是这个过程相当于是 end/2*2(比对两次,两个子节点比较一次,父节点和较大的子节点比较一次。),而end是从末尾到0,整个过程也就是 O ( 1 2 n 2 ) O(\frac{1}{2}n^2) O(21n2)
  • 真正的堆排序,先初始化堆,让整个树状数组满足堆的特性(任意父节点大于子节点,小顶堆相反)后,再把最大元素放到末尾,也即和末尾元素交换之后,只需从新的根节点出发,遇到父节点小于子节点的情况,交换二者,并继续比较交换之后的子节点作为父节点来说有没有出现上述情况。当没有上述情况时,停止。那这一过程,不会大于树高,也即是 O ( l o g 2 n ) O(log_2n) O(log2n)
  • 整体堆排序也就是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
public static void selectHeapSort2(int[] a){
        createHeap(a,a.length-1);
        for (int i = 0; i < a.length; i++) {
            scanOnce(a,0,a.length-1-i);
            swap(a,0,a.length-1-i);
        }
    }

    /**
     * 建堆
     * 从倒数第二层元素开始遍历,凡是子节点大于父节点的,交换顺序
     * @param a
     * @param end
     */
    private static void createHeap(int[] a,int end){

        for (int j = (int)(end-1)/2 ; j >= 0 ; j--) {
            int temp = 2*j+1;
            if (2*j+2<=end && a[temp]<a[2*j+2]){
                temp = 2*j+2;
            }
            if (2*j+1<=end && a[temp]>a[j]){
                swap(a,temp,j);
                scanOnce(a,temp,end);

            }
        }
    }

    /**
     * 扫描,从beg开始,扫描它的孩子,如果大于两孩子,停止;如果小于,交换,继续扫描;直到结束。
     * @param a
     * @param beg
     * @param end
     */
    private static void scanOnce(int[] a,int beg,int end){
        // a[0] >max(a[1],a[2]) return
        // a[0]<max(a[1],a[2]), swap(a,0,maxInd)
        // 继续比较a[maxInd],维护堆的特性
        int j=beg;
        while (2*j+1<=end){
            int temp = 2*j+1;
            if (2*j+2<=end && a[temp]<a[2*j+2]){
                temp = 2*j+2;
            }
            if ( a[temp]>a[j]){
                swap(a,temp,j);
                j = temp;
            }else {
                return;
            }
        }
    }

交换排序

冒泡排序(略)

快速排序

private static void quickSort(int[] arr) {
        quickSortDiv(arr,0,arr.length-1);
    }
    private static void quickSortDiv(int[] a,int low ,int high){
        if(low < high){ //如果不加这个判断递归会无法退出导致堆栈溢出异常
            int middle = partition(a, low, high);
            quickSortDiv(a, low, middle-1);  //递归对低子表递归排序
            quickSortDiv(a, middle + 1, high);  //递归对高子表递归排序
        }
    }
    private static int partition(int a[], int low, int high) {
        int privotKey = a[low]; //基准元素
        while(low < high){   //从表的两端交替地向中间扫描
            while(low < high  && a[high] >= privotKey) { //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端
                high--;
            }
            swap(a, low, high);
            while(low < high  && a[low] <= privotKey ) {
                low++;
            }
            swap(a, low, high);
        }
        return low;
    }

快速排序+插入排序

在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。实践证明,改进后的算法时间复杂度有所降低,且当k取值为 8 左右时,改进算法的性能最佳。

/**
     * 快速排序改进
     * 快速排序+插入排序
     * @param arr
     */
    private static void quickSortAdv(int[] arr) {
        quickSortDivK(arr,0,arr.length-1,8);
        //再用插入排序对基本有序序列排序
        for(int i = 1; i < arr.length; i++){
            int temp = arr[i];
            int j;
            for (j = i - 1; j >= 0 && arr[j] > temp; j--) {
                // 将大于temp的往后移动一位
                arr[j + 1] = arr[j];
            }
            arr[j + 1] = temp;
        }
    }
    private static void quickSortDivK(int[] a,int low ,int high,int k){
        if(low+k < high){ //如果不加这个判断递归会无法退出导致堆栈溢出异常
            int middle = partition(a, low, high);
            quickSortDivK(a, low, middle-1,k);  //递归对低子表递归排序
            quickSortDivK(a, middle + 1, high,k);  //递归对高子表递归排序
        }
    }
    /**
     * 工具方法
     * @param arr
     * @param i
     * @param j
     */
    private static void swap(int[] arr,int i,int j ){
        if(i==j) return;
        arr[i] ^= arr[j];
        arr[j] ^= arr[i];
        arr[i] ^= arr[j];
    }
    private static int partition(int a[], int low, int high) {
        int privotKey = a[low]; //基准元素
        while(low < high){   //从表的两端交替地向中间扫描
            while(low < high  && a[high] >= privotKey) { //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端
                high--;
            }
            swap(a, low, high);
            while(low < high  && a[low] <= privotKey ) {
                low++;
            }
            swap(a, low, high);
        }
        return low;
    }

归并排序

/**
     * 归并排序
     */
    public static void mergeSort(int[] a){
        int[] ano = new int[a.length];
        mergeSort(a,0,a.length-1,ano);
    }
    private static void mergeSort(int[] a,int start,int end,int[] ano){
        if (start>=end) return;

        int mid = start+(end-start)/2;
        int pl = start;
        int pr = mid+1;
        mergeSort(a,start,mid,ano);
        mergeSort(a,mid+1,end,ano);

        //方式一
//        for (int i = 0; i < end - start+1; i++) {
//            if (pl>mid || pr<=end && a[pl]>a[pr]){
//                ano[i] = a[pr++];
//            }else {
//                ano[i] = a[pl++];
//            }
//        }

        //方式二(copy)
        int pn = 0;
        // 把较小的数先移到新数组中
        while (pl <= mid && pr <= end) {
            if (a[pl] < a[pr]) {
                ano[pn++] = a[pl++];
            } else {
                ano[pn++] = a[pr++];
            }
        }
        // 把左边剩余的数移入数组
        while (pl <= mid) {
            ano[pn++] = a[pl++];
        }
        // 把右边边剩余的数移入数组
        while (pr <= end) {
            ano[pn++] = a[pr++];
        }

        //复制排好序的回去
        pl = start;
        for (int i = 0; i < end - start+1; i++) {
            a[pl++] = ano[i];
        }
    }

基数(桶)排序

/**
     * 基数排序/桶排序
     *
     * @param a
     */
    public static void radixSort(int[] a){
        // 找到最大数,确定要排序几趟
        int max = 0;
        for (int i = 0; i < a.length; i++) {
            if (max < a[i]) {
                max = a[i];
            }
        }
        // 判断位数
        int times = 0;
        while (max > 0)  {
            max = max / 10;
            times++;
        }
        // 建立十个队列
        Queue<Integer>[] queues = new Queue[10];
        for (int i = 0; i < 10; i++) {
            queues[i] = new LinkedList<Integer>();
        }
        for (int i = 0; i < times; i++) {
            // 分桶放入
            for (int j = 0; j < a.length; j++) {
                int ind = a[j]/(int)Math.pow(10,i)%10;
                Queue queue = queues[ind];
                queue.offer(a[j]);
            }
            int count = 0;
            //依次拿出放入数组中,因为桶要再利用
            for (int j = 0; j < 10; j++) {
                while (queues[j].size()>0){
                    a[count++]=queues[j].poll();
                }
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值