基础排序算法总结

重点

  • 面试掌握冒泡排序、快速排序、堆排序、归并排序的代码实现与平均时间复杂度即可。
  • shell排序是直接插入的优化,堆排序是直接选择的优化,快速排序是冒泡排序的优化。

排序时间复杂度

参考网站 https://zhuanlan.zhihu.com/p/34982598

类别排序方法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度稳定性
插入排序直接插入 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
插入排序shell排序 O ( n 1.3 ) O(n^{1.3}) O(n1.3) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) 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 ) O(n) O(n) 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 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 ( 1 ) O(1) O(1)稳定
基数排序基数排序 O ( k ∗ n ) O(k*n) O(kn) O ( k ∗ n ) O(k*n) O(kn) O ( k ∗ n ) O(k*n) O(kn) O ( k + n ) O(k+n) O(k+n)稳定

各类排序算法

插入排序

从前向后遍历数组,将后面的数插入前面合适的位置

    // 从小向大排序示例
    public static void insertSort(int[] array) {
        int temp;
        //从第一个数开始向后遍历,将后面的数插到前面已经排序好的数里面
        for (int i = 1; i < array.length; i++) {
            // 记录当前位置数字
            temp = array[i];
            //找到前面的一个数,使它比当前数字小
            while (i >= 1 && array[i - 1] > temp) {
                // 如果不满足,被比较的数字向右移一位
                array[i] = array[i - 1];
                //不断向左,直到找到或最左
                i--;
            }
            //退出了循环说明找到了合适的位置了,将当前数据插入合适的位置中
            array[i] = temp;
        }
    }

shell 排序

参考网站 https://www.cnblogs.com/chengxiao/p/6104371.html
这个又叫希尔排序,实现原理上就是分组排序的插入排序

    // 把数组元素分成若干组,最开始每组2个数,然后4个...最后整个数组有序
    public static void shellSort(int[] arrays) {
        //增量每次都/2
        for (int step = arrays.length / 2; step > 0; step /= 2) {
            //从增量那组开始进行插入排序,直至完毕
            for (int i = step; i < arrays.length; i++) {
                int j = i;
                int temp = arrays[j];
                // j - step 就是代表与它同组相邻的元素
                while (j - step >= 0 && arrays[j - step] > temp) {
                    arrays[j] = arrays[j - step];
                    j = j - step;
                }
                arrays[j] = temp;
            }
        }
    }

直接选择排序

从未排序的数中找到合适的数,放在排序的数后

    public static void chooseSort(int[] arr) {
        // 依次去找第一个数至倒数第二个数
        for (int i = 0; i < arr.length - 1; ++i) {
            // 将i看做当前位置,并将剩余数字中最小值的下标初始化为最小值的下标
            int min = i;
            // 找到未排序数字中最小的那个数,并记录下标
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[min]) {
                    min = j;
                }
            }
            // 交换当前位置的数与最小位置的数
            if (min != i) {
                int temp = arr[i];
                arr[i] = arr[min];
                arr[min] = temp;
            }
        }
    }

堆排序

堆排序是选择排序的加强版。在从小到大排序中,它将数组中映射为一个最大堆,数组末尾的数字自然是最大的。然后将最大的数字排除,重新建堆,最后完成排序。

    // 向堆中插入值,建立最大堆
    private static void heapify(int[] array, int currentRootNode, int size) {
        if (currentRootNode < size) {
            //左子树和右子树的位置
            int left = 2 * currentRootNode + 1;
            int right = 2 * currentRootNode + 2;
            //把当前父节点位置看成是最大的
            int max = currentRootNode;
            if (left < size) {
                //如果比当前根元素要大,记录它的位置
                if (array[max] < array[left]) {
                    max = left;
                }
            }
            if (right < size) {
                //如果比当前根元素要大,记录它的位置
                if (array[max] < array[right]) {
                    max = right;
                }
            }
            //如果最大的不是根元素位置,那么就交换
            if (max != currentRootNode) {
                int temp = array[max];
                array[max] = array[currentRootNode];
                array[currentRootNode] = temp;

                //继续比较,直到完成一次建堆
                heapify(array, max, size);
            }
        }
    }

    // 把数组建成一个堆
    public static void maxHeapify(int[] array, int size) {
        // 从数组的尾部开始,直到第一个元素(角标为0)
        for (int i = size - 1; i >= 0; i--) {
            heapify(array, i, size);
        }
    }

    //堆排序
    public static void heapSort(int[] array) {
        for (int i = 0; i < array.length; i++) {
            //每次建堆就可以排除一个元素了
            maxHeapify(array, array.length - i);
            //交换
            int temp = array[0];
            array[0] = array[(array.length - 1) - i];
            array[(array.length - 1) - i] = temp;
        }
    }

冒泡排序

对每个数字进行两两交换,一次冒泡将一个数字归位。

    // 冒泡排序  https://www.cnblogs.com/jyroy/p/11248691.html
    // 一般能写出标志位过面试就没问题
    public static void bubbleSort(int[] array) {
        boolean isChanged = false;

        for (int i = 0; i < array.length - 1; i++) {
            isChanged = false;
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    isChanged = true;
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
            // 如果当前数字没有改变过位置,说明所有数字都已经有序了
            if (!isChanged) {
                return;
            }
        }
    }

快速排序

快速排序使用分治,一次分治下来小的在左边,大的在右边。递归重复这个过程,使整个数组有序。

    // 默认使用最左边的数作为中间点
    // 一次分治后,比中间点小的在左边,大的在右边
    private static int partition(int[] array, int low, int high) {
        //固定的切分方式
        int key = array[low];
        while (low < high) {
            // 注意两个while中判断key大小关系时不能全加上等于条件,否则当开头和结尾都是最值的时候会出错
            while (array[high] > key && high > low) {//从后半部分向前扫描
                high--;
            }
            if(low < high){
        		array[low] = array[high];
     		}
            while (array[low] <= key && high > low) {//从前半部分向后扫描
                low++;
            }
            if(low < high){
        		array[high] = array[low];
      		}
        }
        array[high] = key;
        return high;
    }

    // 递归重复分治过程,使范围内每个数有序
    public static void quickSort(int[] array, int low, int high) {
        if (low >= high || array == null) {
            return;
        }
        int index = partition(array, low, high);
        quickSort(array, low, index - 1);
        quickSort(array, index + 1, high);
    }

    //快速排序
    public static void quickSort(int[] array) {
        quickSort(array, 0, array.length - 1);
    }

归并排序

先将数组拆成最小长度为1的小数组,然后小数组合并为长度为2的数组,长度为2的数组合并为成长度为4的数组…最后整个数组完整排序。

    private static void merge(int[] array, int left, int right, int mid, int[] temp) {
        int i = left;//左序列指针
        int j = mid + 1;//右序列指针
        int t = 0;//临时数组指针
        while (i <= mid && j <= right) {
            if (array[i] <= array[j]) {
                temp[t++] = array[i++];
            } else {
                temp[t++] = array[j++];
            }
        }
        while (i <= mid) {  //将左边剩余元素填充进temp中
            temp[t++] = array[i++];
        }
        while (j <= right) {  //将右边剩余元素填充进temp中
            temp[t++] = array[j++];
        }
        t = 0;
        while (left <= right) {
            array[left++] = temp[t++];
        }
    }

    public static void mergeSort(int[] array, int left, int right, int[] temp) {
        if (left >= right || array == null) {
            return;
        }
        int mid = (left + right) / 2;
        mergeSort(array, left, mid, temp);
        mergeSort(array, mid + 1, right, temp);
        merge(array, left, right, mid, temp);
    }

    //归并排序
    public static void mergeSort(int[] array) {
        //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
        int[] temp = new int[array.length];
        //递归版本 调用上面的重载方法
//        mergeSort(array,0,array.length-1,temp);
        //非递归版本
        int len = 1;
        while (len <= array.length) {
            for (int i = 0; i + len <= array.length; i += len * 2) {
                int left = i, mid = i + len - 1, right = i + 2 * len - 1;
                if (right > array.length - 1) {
                    right = array.length - 1;
                }
                merge(array, left, right, mid, temp);
            }
            len = len * 2;
        }
    }

基数排序

从个位开始向桶子里面分配,然后按个位从小到大向数组里面填数。接着是十位,十位同样下放到桶子后按十位大小从小到大向数组中填数,同时覆盖原有个位的结果。然后是百位、千位…重复这个过程直到最大数字的最高位也下放到桶子里面。这次回收后数组排序完成。

    // 基数排序 参考网站 https://segmentfault.com/a/1190000013986116
    public static void radixSort(int[] arrays) {
        // 找到数组中的最大值
        int max = arrays[0];
        for (int i = 1; i < arrays.length; ++i) {
            max = Math.max(arrays[i], max);
        }

        // 需要遍历的次数由数组最大值的位数来决定
        for (int i = 1; max / i > 0; i = i * 10) {
            int[][] buckets = new int[arrays.length][10];
            //获取每一位数字(个、十、百、千位...分配到桶子里)
            for (int j = 0; j < arrays.length; j++) {
                int num = (arrays[j] / i) % 10;
                //将其放入桶子里
                buckets[j][num] = arrays[j];
            }

            //回收桶子里的元素
            int k = 0;
            //有10个桶子
            for (int j = 0; j < 10; j++) {
                //对每个桶子里的元素进行回收
                for (int l = 0; l < arrays.length; l++) {
                    //如果桶子里面有元素就回收(数据初始化会为0)
                    if (buckets[l][j] != 0) {
                        arrays[k++] = buckets[l][j];
                    }
                }
            }
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值