十大排序算法整理


1 概述

1.1 分类与时间复杂度

在这里插入图片描述
在这里插入图片描述

1.2 稳定排序、原地排序、时间复杂度、空间复杂度

  • 稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。

  • 非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。

  • 原地排序:原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。

  • 非原地排序:需要利用额外的数组来辅助排序。

  • 时间复杂度:一个算法执行所消耗的时间

  • 空间复杂度:运行完一个算法所需的内存大小

  • 稳定的好处:如果我们只对一串数字排序,那么稳定与否确实不重要,因为一串数字的属性是单一的,就是数字值的大小。但是排序的元素往往不只有一个属性,例如我们对一群人按年龄排序,但是人除了年龄属性还有身高体重属性,在年龄相同时如果不想破坏原先身高体重的次序,就必须用稳定排序算法.

2 冒泡排序(✓)

在这里插入图片描述

  • 前后两个数比较,大的交换到后面,最后就能在最后位置得到最大的数

  • 找大的放最后

  • 重点1:大循环次数 = 数组长度 - 1;小循环次数 = 数组长度 - 1 - i (每次都比第几趟大循环次数少1)

  • 重点2:每次排序都把最大的放在最后,所以排序i轮,说明i个数已经排好了,就不需要再比较了,所以 array.length - 1 - i

  • 重点3:如果某一次大循环比较没有发生交换,那么此时就表示已经是有序的了,可以直接退出,此时整个数组就是从小到大有序的,比如下面的第二趟排序之后就已经有序了,就不用第三,四趟了
    在这里插入图片描述

  • 如上图,第二趟排序之后,没有发生交换,说明找到20最大值后,已经有序了,之后就不用排序了,直接return退出函数即可

// 冒泡排序
    public static void bubbleSort(int[] array) {
        if (array == null || array.length < 2) {
            return;
        }

        int temp = 0;   // 存放交换值
        boolean flag = false;   // true表示交换了

        // 大循环次数是数组长度 - 1
        for (int i = 0; i < array.length - 1; i++) {

            // 小循环次数 = 大循环 - i = array.length - 1 - i (每次排序后就会少i个数,因为他们是每次找的最大值,所以不用再排了)
            for (int j = 0; j < array.length - 1 - i; j++) {

                // 如果前一个 > 后一个 , 则交换位置,否则表示有序了
                if (array[j] > array[j + 1]) {
                    temp = array[j + 1];
                    array[j + 1] = array[j];
                    array[j] = temp;

                    // 发生交换
                    flag = true;
                }
            }

            // 如果没有交换元素,说明已经有序
            if (flag==false) {
                return;
            } else {
                flag = false; // 说明交换了,但是交换了就要继续冒泡排序,所以需要把flag重置,判断下次是否没交换
            }
        }
    }

3 快速排序(✓)

参考

在这里插入图片描述


import java.util.Arrays;

public class QuickSortDemo {
    public static void main(String[] args){
        int[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
        quickSort(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }


    public static void quickSort(int[] arr,int left,int right){
        if(left>right){
            return;
        }

        // i,j用来暂存left,right
        // pivot是基准值,默认数组第一个元素
        int i=left;
        int j=right;
        int pivot = arr[left];

        while (i<j) {
            // j先开始动,往左边找一个小于pivot的值,所以while的条件是 array[j] >= pivot
            // 因为要找小的数,所以如果array[j]>pivot,就继续j--,直到找到小于pivot的数,此时j停下来
            while (arr[j]>=pivot && i<j) {
                j--;
            }

            // i从左往右,找一个大于pivot的数,所以while条件是 array[i] <= pivot
            // 因为要找大于pivot的数,所以如果小于=pivot就继续找,直到找到大于pivot的数,此时i停下来
            while (arr[i]<=pivot && i<j) {
                i++;
            }

            // 此时i,j都已经找到了一个符合条件的数,然后就是交换他们
            //如果满足条件则交换,i<j才可以,i=j的时候就表示找到了pivot的位置
            if (i<j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }

            // 上面一轮结束仅仅是找到一组符合条件的数,还要继续找,直到i<j不满足为止
            // while退出后,彼时 i = j,此时的i,j位置就是pivot应该呆的位置
        }

        //最后将基准值 与 i和j相等的位置 的 数字交换
        arr[left] = arr[i]; // array[left] 此时就是 pivot,此句相当于 pivot = array[i],但是这样修改了pivot,交换2个数要temp,array[left]相当于temp
        arr[i] = pivot;

        // 此时可以检查 i == j是否成立,来验证猜想
        System.out.println("i==j? " + (i==j) );

        //递归调用左半数组,j-1,因为j的位置就是pivot的位置,j位置不需要参与排序了
        quickSort(arr, left, j-1);

        //递归调用右半数组,j+1原因同上
        quickSort(arr, j+1, right);
    }
}

4 插入排序(✓)

在这里插入图片描述

  • 思想:把n个待排元素看成一个有序表,一个无序表,开始时有序表只有一个元素(第一个),无序表有n-1元素,每次从无序表中取出第一个元素,与有序表中元素比较,插入正确位置,使其称为新的有序表。

  • https://www.cnblogs.com/xiaoming0601/p/5862793.html

package Sort;

import java.util.Arrays;

public class InsertSortDemo {

    public static void main(String[] args) {
        int[] array = {11, 22, 9, 2};
        insertSort(array);
    }


    public static void insertSort(int[] arr)
    {
        int i, j;	// j是需要插入的位置
        int n = arr.length; // 数组长度
	    int target;         // 未排序序列第一个数,它是需要插入的数

        //假定第一个元素(0索引)被放到了正确的位置上
        //这样,仅需遍历1 - n-1
        for (i = 1; i < n; i++)
        {
            j = i;
            target = arr[i];	// 把需要插入的数保存一下

            while (j > 0 && target < arr[j - 1])	
            // 索引大于0,目标值比他前一个值小,表示没找到他该插入的位置,所以继续比较他前2个(j--)
            // 并且需要把他前一个数后移一位,直接覆盖target(target已经被保存了。)
            {
                arr[j] = arr[j - 1];
                j--;
            }

            arr[j] = target;	// target插入找到的位置
            System.out.println(Arrays.toString(arr));
        }
    }
}

5 希尔排序(✓)

  • 它是插入排序的高级版,回顾一下插入排序

    • 将数据插入到已有序的数列中

    • 排序前:将每个元素看成有序的数列

      • 第一趟排序后:得到一个有序数列,其长度为2

      • 第二趟排序后:得到一个有序数列,其长度为3

      • 第三趟排序后:得到一个有序数列,其长度为4

      • ………每一趟插入排序,都可以将一个无序值插入一个有序数列,直至全部值有序

    • 如果给出的数组足够乱的话,那么插入排序所耗费的时间是O(n^2)

    • 既然希尔排序是插入排序的高级版,那它做了哪些优化呢??让我们来看看:

    • 希尔排序在排序前:将一个序列分成了好几个序列

    • 在第一趟排序时:将这几个序列做插入排序。排序后,部分较大的数字往后靠,部分较小的数字往前靠

    • 在第二趟排序时:将这个序列又分了好几个序列做插入排序(但比第一次分的数要少,ps:如果第一次分5个,第二次可能就2个了)。排序后,部分较大的数字往后靠,部分较小的数字往前靠

    • …………….

    • 在第n趟排序时:将这个序列又分了好几个序列(直到剩下一个序列),从宏观上看,此序列就基本是有序的了。这时就用简单插入排序将数列直至已序

    • 从直观上看希尔排序:

      • 就是把数列进行分组(不停使用插入排序),直至从宏观上看起来有序,最后插入排序起来就容易了(无须多次移位或交换)。
    • 那么,上面那里说了将一个序列分成好几个序列,那么到底怎么分呢?比如有10个元素的序列,分成几个才合适?每次缩减又是多少呢?从专业的角度上讲,将一个序列分成好几个序列,用一个数来表示:那个数称为增量。显然的是,增量是不断递减的(直到增量为1)。往往的:如果一个数列有10个元素,我们第一趟的增量是5,第二趟的增量是2,第三趟的增量是1。如果一个数列有18个严肃,我们第一趟的增量是9,第二趟的增量是4,第三趟的增量是2,第四趟的增量是1。很明显我们可以用一个序列来表示增量:{n/2,(n/2)/2…1},每次增量都/2

在这里插入图片描述

    public static void shellSort(int[] arrays) {


        //增量每次都/2
        for (int step = arrays.length / 2; step > 0; step /= 2) {

            //从增量那组开始进行插入排序,直至完毕
            // i<length:因为step是length一半,i到length长度就是总的次数
            for (int i = step; i < arrays.length; i++) {

                int j = i;	// 因为后面要减step,所以不能直接操作i,用j。
                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;	// 此时j已经减过step了
            }
        }


    }

6 选择排序(✓)(算法图解解释更好)

  • 核心:在算法图解中,选择排序就是每次选最小值放入一个新数组,我们使用的代码没有用新数组,而是直接在原数组操作,但其实原理一样,就是每次选最小(大)的,添加新数组。
  • 一个无序序列,每次找到最小的和第一个位置交换(从小到大排序)
  • 与冒泡相比,每次只交换一次
  • 一开始假设第一个最小,后面都无序,后面找到最小和假设最小交换位置,然后最小的那数就有序,从下个位置开始假设最小,后面无序与其比较找最小加入一开始找到最小的那个有序序列。

在这里插入图片描述


import java.util.Arrays;

public class SelectSortDemo {
    public static void main(String[] args) {
        int[] array = {11, 22, 9, 2};
        selectSort(array);
    }

    public static void selectSort(int[] array){
        // 选出未排序中最小的数与第一个数交换
        if (array.length == 0){
            return;
        }

        for (int i = 0; i < array.length - 1; i++) {    // 假设最小值是i,最后只有一个数的时候它肯定最小,所以只需要 [0 , length-1)。
            int minIndex = i;
            // 找到每轮真的最小值,与第一个猜想最小值交换位置
            for (int j = i+1 ; j < array.length; j++) { // 自己不用和自己比较,所以j=i+1,要比较到最后一个数,所以 j<length
                if (array[j] < array[minIndex]) //找到最小的数
                    minIndex = j; //将最小数的索引保存
            }
            // 上面一轮结束,就找到了未排序序列中的最小值,把它和假设最小值交换位置。
            int temp = array[i];	// 保存假设的最小值
            array[i] = array[minIndex];	// 真的最小值给i
            array[minIndex] = temp;		// i给真的最小值
            System.out.println(Arrays.toString(array));
        }
    }
}



7 堆排序(✓)

什么是堆:

https://baike.baidu.com/item/%E5%A0%86/20606834?fr=aladdin
https://www.jianshu.com/p/6b526aa481b1
https://blog.csdn.net/qq_34270874/article/details/113091364

堆排序

  • https://mp.weixin.qq.com/s/TKRtF2dAtH7VuNs-FC4awA
  • https://mp.weixin.qq.com/s/B0ImTjuQJiR7ahRzBpslcg

8 归并排序(✓)

  • 归并的含义是将两个或两个以上的有序表合并成一个新的有序表。两个就是二路归并,代码以二路归并为例。
  • 思路1:新建1个表tmp存放最终的数据,拆分后直接比较大小,小的放入tmp,最后把tmp拷贝给原数组。
  • 思路2:新建2个表存放拆分后的数据,比较后直接覆盖原数组。
  • 参考

在这里插入图片描述


import java.util.Arrays;

public class MergeSortDemo {

        public static void main(String[] args) {
            int[] arr = {11,44,23,67,88,65,34,48,9,12};
            
            // 先新建一个临时数组存放合并的数据,最后可以就使用tmp,也可以把tmp的元素在复制回原arr,且避免频繁开辟新数组空间
            int[] tmp = new int[arr.length];    

            mergeSort(arr,0,arr.length-1, tmp);

            System.out.println(Arrays.toString(tmp));
            System.out.println(Arrays.toString(arr));
        }

        public static void mergeSort(int[] arr, int left, int right, int[] tmp){
            if(left<right){
                int mid = (left+right)/2;   // 二路归并所以 /2
                mergeSort(arr,left,mid, tmp); // 继续拆分左边
                mergeSort(arr,mid+1, right, tmp);  // 继续拆分右边

                // 合并拆分后的2个序列,因为上面有递归,所以上面递归结束后,merge从合并成2个 -> 合并成4个 ...
                merge(arr, left, mid, right, tmp);
            }
        }

        public static void merge(int[] arr, int left, int mid, int right, int[] tmp){
            
            // tmp数组的索引
            int i = 0;
            
            //左边序列x 和 右边序列起始索引y
            int x = left; 
            int y = mid+1;  
            
            while(x <= mid && y <= right){
                
                // 比较2个序列元素那个小,就放入新tmp数组,然后索引+1,继续比较
                if(arr[x] < arr[y]){
                    tmp[i] = arr[x];
                    x++;
                    i++;
                }else{
                    tmp[i] = arr[y];
                    y++;
                    i++;
                }
            }

            // 若左边序列还有剩余,它们肯定是比较大的数,直接放tmp末尾。
            while(x <= mid){
                tmp[i++] = arr[x++];
            }
            // 同上面理,如果右边序列有剩余,也是大的,直接放tmp末尾
            while(y <= right){
                tmp[i++] = arr[y++];
            }

            // 把tmp的数据复制回原数组arr,i是tmo数组大小
            for(int t=0; t<i; t++){
                arr[left+t] = tmp[t];
            }
        }


}

9 计数排序(✓)

  • 参考

  • 核心思想:一个待排序列,找最小值和最大值,他们之前的区间就是需要的数组长度,适用于取值范围不大的情况。

10 桶排序(✓)

  • 桶排序就是把最大值和最小值之间的数进行瓜分,例如分成 10 个区间,10个区间对应10个桶,我们把各元素放到对应区间的桶中去,再对每个桶中的数进行排序,可以采用归并排序,也可以采用快速排序之类的。之后每个桶里面的数据就是有序的了,我们在进行合并汇总。
  • 参考

11 基数排序(✓)

10 大排序算法参考1

10 大排序算法参考2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值