十大排序算法总结

作为一名刚上大三的学生,还是希望能目光长远一点,将目标定于明年的春招,所谓,宝剑锋从磨砺出,梅花香自苦寒来,相信很多同学跟我一样,都已经开始准备明年的春招了,所以,博主希望跟大家一起努力,收获成功!

冒泡排序

在这里插入图片描述

public static int[] bubbleSort(int[] array){

    int length = array.length;
    for(int i = 0;i < length - 1;i++){
        for(int j = i + 1;j < length - i - 1;j++){
            if(array[j] > array[j+1]){
                int temp = array[j];
                array[j] = array[j+1];
                array[j + 1] = temp;
            }
        }
    }
    return array;
}
  • 在乱序的条件下的时间复杂度是O(n^2),有序情况下的时间复杂度是O(n)
  • 稳定排序
  • 空间复杂度:O(1)

选择排序

算法思路:

首先找到数组中的最小元素,然后将这个最小元素和数组的第一个元素交换位置,如果第一个元素就是最小元素,就和自己交换位置;再次,在剩下的元素中找到最小元素和数组中的第二个元素交换位置,如此往复,直到将整个数组排序,一句话总结就是,不断在剩余元素中找最小元素

    /**
     * 选择排序
     *
     * @param array
     * @return
     */
    public static int[] selectSort(int[] array) {
        int length = array.length;
        System.out.println("length:" + length);
        //找出最小下标
        //外层循环指向当前元素
        for (int i = 0; i < length - 1; i++) {
            //最小下标
            int minIndex = i;
            //内层循环负责找到最小元素
            for (int j = i + 1; j < length; j++) {
                //跟最小下标对应数比较
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            //将找到的最小值和i位置所在的值进行交换
            if (i != minIndex) {
                int tmp = array[i];
                array[i] = array[minIndex];
                array[minIndex] = tmp;
            }
        }
        return array;
    }
  • 不稳定排序
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 选择排序是数据移动最少的,每次交换两会改变两个数组元素的值,因此选择排序用了N次交换,交换次数和数组大小是线性关系

快速排序

算法思路

快速排序是将一个数组分成两个子数组,将两个子数组分别独立排序,当两个子数组有序时数组就有序,需要找到一个基准值来划分数组成两个子数组,分别是比基准值大和比基准值小,递归的进行排序

    public static void quickSort(int[] nums, int left, int right) {
        if (left < right) {
            int low = left;
            int hight = right;
            int key = nums[left];
            while (low < hight) {
                //从高位向低位
                while (low < hight && nums[hight] >= key) {
                    hight--;
                }
                if (low < hight) {
                    nums[low++] = nums[hight];
                }
                //低位向高位
                while (low < hight && nums[low] < key) {
                    low++;
                }
                if (low < hight) {
                    nums[hight--] = nums[low];
                }
            }
            nums[low] = key;
            quickSort(nums, left, low - 1);
            quickSort(nums, low + 1, right);
        }
    }

python实现

def quickSort(array):
    if len(array) < 2:
        return array
    else  
        pivot = array[0]
        less = [i for i in array[1:] if i <= pivot]
        greater = [i for i in array[1:] if i > pivot]
        return quickSort(less) + [pivot] + quickSort(greater)    
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 不稳定排序
  • 算法思想:分治

快速排序优化:

  • 对于小数组,快速排序比插入排序要慢,所以当对小数组进行排序时,可以切换到插入排序

归并排序

算法思路:

归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序

在这里插入图片描述

在这里插入图片描述

    /**
     * 归并排序
     * 时间复杂度:O(n*logn)
     * @param oldArray
     * @param tmpArray
     * @param left
     * @param right
     */
    public static void mergeSort(int[] oldArray,int[] tmpArray,int left,int right){

        if(left < right){
            int center = (left + right) / 2;
            //分治排序左子数组
            mergeSort(oldArray,tmpArray,left,center);
            //分治排序右子数组
            mergeSort(oldArray,tmpArray,center+1,right);
            //分开排序后合并成一个新数组在copy回原数组,此时原数组已经有序
            merge(oldArray, tmpArray, left, center+1,right);
        }
    }

    private static void merge(int[] oldArray, int[] tmpArray, int leftStart, int rightStart, int rightEnd)  {

        //左数组结束下标
        int leftEnd = rightStart - 1;
        //辅助数组起始下标
        int tmpPos = leftStart;
        //数组元素数量
        int numElements = rightEnd - tmpPos + 1;
        //左右数组进行比较,按序放入辅助数组
        while(leftStart <= leftEnd && rightStart <= rightEnd){
            if(oldArray[leftStart] <= oldArray[rightStart]){
                tmpArray[tmpPos++] = oldArray[leftStart++];
            }else{
                tmpArray[tmpPos++] = oldArray[rightStart++];
            }
        }
        //检查是否还有剩下元素
        while(leftStart <= leftEnd){
            tmpArray[tmpPos++] = oldArray[leftStart++];
        }
        while(rightStart <= rightEnd){
            tmpArray[tmpPos++] = oldArray[rightStart++];
        }
        //复制回原来旧数组
        for(int i = 0;i<numElements;i++,rightEnd--) {
            oldArray[rightEnd] = tmpArray[rightEnd];
        }
    }
  • 算法思想:分治
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
  • 稳定排序

堆排序

堆排序是基于二叉堆(优先队列)实现的,通过建立最大堆或者最小堆,删除堆顶的最值元素后将堆顶元素放入指定集合,重新建堆,最后集合便是有序序列

在这里插入图片描述

    /**
     * 堆排序:【最小堆】 --- 降序
     * - 建堆
     * - 删除堆顶
     * - 循环以上两步
     *
     * @param arr
     */
    public void heapSortOfMinHeap(int[] arr) {
        int len = arr.length;
        buildMinHeap(arr, len);
        while (len > 1) {
            swap(arr, 0, len - 1);
            len--;
            heapifyForMinHeap(arr, 0, len);
        }
    }
    

    /**
     * 构建最小堆
     *
     * @param arr
     * @param len
     */
    private void buildMinHeap(int[] arr, int len) {
        for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
            heapifyForMinHeap(arr, i, len);
        }
    }


    /**
     * 构建最小堆
     *
     * @param arr
     * @param i
     * @param len
     */
    private void heapifyForMinHeap(int[] arr, int i, int len) {
        //左子节点
        int left = i * 2 + 1;
        //右子节点
        int right = i * 2 + 2;
        //辅助变量 -- 根节点
        int minest = i;
        //节点比较,小节点上浮
        if (left < len && arr[left] < arr[minest]) {
            minest = left;
        }
        if (right < len && arr[right] < arr[minest]) {
            minest = right;
        }
        //完成交换
        if (minest != i) {
            swap(arr, i, minest);
            heapifyForMinHeap(arr, minest, len);
        }
    }


    /**
     * 交换函数
     *
     * @param array
     * @param i
     * @param j
     */
    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
  • 不稳定排序
  • 时间复杂度:O(nlogn)

插入排序

算法思路:

与选择排序一样,当前索引左边的所有元素都是有序的,但是他们的最终位置还不确定,为了给更小的元素腾出空间,他们可能会被移动,但是当索引到达数组的末端,数组排序就完成了

在这里插入图片描述

    /**
     * 插入排序
     * @param array
     */
    public static void insertSort(int[] array){
        int j;
        for(int p = 1;p < array.length;p++){
            int temp = array[p];
            //将array[p]插入到array[p-1],array[p-2]....
            for(j = p;j > 0 && temp < array[j-1];j--){
                array[j] = array[j - 1];
            }
            //完成插入
            array[j] = temp;
        }
    }
  • 稳定排序
  • 时间复杂度分析:O(N^2),如果序列在排序前已经是有序序列,则为O(N)
  • 空间复杂度分析:O(1)
  • 数据量较少时效率高。插入排序适合数据量少的情况
  • 算法的实际运行效率优于选择排序和冒泡排序。
  • 插入排序对于部分有序的数组很有效,部分有序的数组类如数组中每个元素距离他的最终位置不远,一个有序的大数组接一个小数组,数组中只有几个元素的位置不正确

希尔排序

算法思想:

希尔排序是一种基于插入排序的快速排序算法,对于大规模的数据,插入排序很慢,因为随着数据规模的增大,移动规模也可能随着增大,因为插入排序只会交换相邻的元素,元素只能一点点的从数组一端移动到指定位置,但是希尔排序是使用一种步长的思想,根据指定步长分为h个子数组,每个子数组进行插入排序,形成了h个有序子数组

在这里插入图片描述

    /**
     * 希尔排序
     * 不稳定排序
     * @param array
     */
    public static void hashSort(int[] array){
        int j;
        //外层循环控制步长
        for(int grap = array.length / 2;grap > 0;grap/=2){
            //内层循环其实是插入排序
            for(int p = grap;p < array.length;p++){
                int temp = array[p];
                for(j = p;j >= grap && temp < array[j - grap];j-=grap){
                    array[j] = array[j - grap];
                }
                array[j] = temp;
            }
        }
    }
  • 不稳定排序

  • 希尔排序的时间复杂度较直接插入排序低,它的时间是所取“增量”(步长gap)序列的函数。

    最好时间复杂度: O(n) – 有序情况下

    平均时间复杂度: O(1.2^n ~ 1.5^n) – Hibbard

    最坏时间复杂度: O(n^2) — 希尔增量

  • 空间复杂度:O(1)

桶排序

将序列中的元素分配到各自的桶。
对每个桶内的元素进行排序。可以选择任意一种排序算法。
将各个桶中的元素合并成一个大的有序序列。

在这里插入图片描述

    /**
     * 桶排序
     * @param data
     * @return
     */
    public static List<ArrayList<Integer>> bucketSort(int[] data){
        int max = getMax(data);
        int min = getMin(data);
        //桶的数量取到最大,可以取max - min / array.length + 1
        //int bucketNum = max - min + 1;
        int bucketNum = max - min / data.length + 1;
        ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
        //初始化桶
        for(int i = 0;i < bucketNum;i++){
            bucketArr.add(new ArrayList<>());
        }
        //元素放入桶中
        for(int i = 0;i < data.length;i++){
            int tmp = data[i] - min / data.length;
            bucketArr.get(tmp).add(data[i]);
        }
        //每个桶分别排序
        for(int i = 0;i < bucketNum;i++){
            Collections.sort(bucketArr.get(i));
        }
        return bucketArr;
    }

    /**
     * 获取待排序数组中的最大值
     * @param array
     * @return
     */
    private static int getMax(int[] array){

        int max = array[0];
        for(int i = 1;i<array.length;i++) {
            if (array[i] > max) {
                max = array[i];
            }
        }
        return max;
    }

    /**
     * 获取待排序数组的最小值
     * @param array
     * @return
     */
    private static int getMin(int[] array){

        int min = array[0];
        for(int i = 1;i<array.length;i++) {
            if (array[i] < min) {
                min = array[i];
            }
        }
        return min;
    }

    public static void main(String[] args) {
        int[] array = {1,5,2,5,8,3};
        List<ArrayList<Integer>> list = bucketSort(array);
        ArrayList<Integer> arrayList = new ArrayList<>();
        for(ArrayList list1 : list){
            arrayList.addAll(list1);
        }
        for(int i : arrayList){
            System.out.println(i);
        }
    }
  • 桶排序时间复杂度可以认为接近O(n)
  • 空间复杂度取决于桶的数量
  • 桶排序是稳定排序,但是当桶内的排序是其他不稳定的排序,例如快速排序时就不再稳定了

计数排序

在这里插入图片描述

    /**
     * 计数排序
     * 稳定排序
     * @param array
     */
    public static void countSort(int[] array){
        //拿出最大值和最小值
        int max = getMax(array);
        int min = getMin(array);
        //建立辅助数组
        int[] help = new int[max - min + 1];
        //array数组中每个数和最小值的差都是一定的,根据差找到对应的数组下标,开始计数
        for(int i = 0;i < array.length;i++){
            int j = array[i] - min;
            help[j]++;
        }
        int k = 0;
        //恢复数组
        for(int i = 0;i < help.length;i++){
            while(help[i] != 0){
                array[k] = i + min;
                k++;
                help[i]--;
            }
        }
    }

    private static int getMin(int[] array) {
        int min = array[0];
        for(int i = 0;i < array.length;i++){
            if(min > array[i]){
                min = array[i];
            }
        }
        return min;
    }

    private static int getMax(int[] array){
        int max = array[0];
        for(int i = 0;i < array.length;i++){
            if(max < array[i]){
                max = array[i];
            }
        }
        return max;
    }
  • 稳定排序
  • 其空间复杂度和时间复杂度均为O(n+k)线性时间复杂度,其中k是整数的范围(取决于辅助数组大小)
  • 非比较排序
  • 计数排序其实是桶数取 max - min + 1最大时的桶排序

基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。他是根据键值的每位数字来分配桶;然后每一位每一位的比较

  1. 最低位优先法,简称LSD法:先从最低位开始排序,再对次低位排序,直到对最高位排序后得到一个有序序列;

  2. 最高位优先法,简称MSD法:先从最高位开始排序,再逐个对各分组按次高位进行子排序,循环直到最低位。

在这里插入图片描述


    public static int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }

    /**
     * 获取最高位数
     */
    private static int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }

    private static int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

    protected static int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        return lenght;
    }

    private static int[] radixSort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;

        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
            int[][] counter = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j++) {
                int bucket = ((arr[j] % mod) / dev) + mod;
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);
            }

            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket) {
                    arr[pos++] = value;
                }
            }
        }

        return arr;
    }

    /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private static int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }

    public static void main(String[] args) throws Exception {
        int[] a = {1,8,2,6,4,2,3,8,4,6,10,12,45,21,31,22,22,22,23,21,20,23,24,21,23,23};
        a = sort(a);
        for(int i = 0;i<a.length;i++) {
            System.out.print(a[i]+"\t");
        }
    }
  • 基数排序是稳定排序,在某些时候,基数排序法的效率高于其它的稳定性排序法。
  • 时间复杂度:O(N)
  • 空间复杂度:O(N)

基数排序,计数排序其实是桶排序的特殊情况:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

整理

在这里插入图片描述

排序算法最好时间复杂度最坏时间复杂度空间复杂度内部or外部排序
冒泡排序O(n)O(n^2)O(1)内部排序
选择排序O(n^2)O(n^2)O(1)内部排序
插入排序O(n)O(n^2)O(1)内部排序
计数排序O(n+k)O(n+k)O(k)外部排序
基数排序O(nk)O(nk)O(n+k)外部排序
桶排序O(n+k)O(n^2)O(k)外部排序
希尔排序O(nlogn)O(1)内部排序
归并排序O(nlogn)O(nlogn)O(1)内部排序
快速排序O(nlogn)O(n^2)O(1)内部排序
堆排序O(nlogn)O(nlogn)O(1)内部排序
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值