【数据结构】七大排序算法详解

news2024/11/19 13:14:32

目录

♫什么是排序

♪排序的概念

♪排序的稳定性

♪排序的分类

♪常见的排序算法

♫直接插入排序

♪基本思想

♪算法实现

♪算法稳定性

♪时间复杂度

♪空间复杂度

♫希尔排序

♪基本思想

♪算法实现

♪算法稳定性

♪时间复杂度

♪空间复杂度

♫直接选择排序

♪基本思想

♪算法实现

♪算法稳定性

♪时间复杂度

♪空间复杂度

♫堆排序

♪基本思想

♪算法实现

♪算法稳定性

♪时间复杂度

♪空间复杂度

♫冒泡排序

♪基本思想

♪算法实现

♪算法稳定性

♪时间复杂度

♪空间复杂度

♫快速排序

♪基本思想

♪算法实现

♪算法的优化

♪算法稳定性

♪时间复杂度

♪空间复杂度

♫归并排序

♪基本思想

♪算法实现

♪算法的稳定性

♪时间复杂度

♪空间复杂度

♫排序算法的比较


♫什么是排序

♪排序的概念

排序是将一组数据,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

♪排序的稳定性

将一组数据进行排序后,如果原数据与排序后的数据中相同元素的相对位置仍然保持不变,那么该排序就是一个稳定的排序,否则就不是一个稳定的排序。

注:一个本身稳定的排序可以通过不稳定的算法实现,而一个不稳定的排序是不能通过稳定的算法实现的。

♪排序的分类

排序分为内部排序和外部排序两种:

1. 内部排序:数据元素全部放在内存中的排序。
2. 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序

♪常见的排序算法

常见的排序算法有插入排序(直接插入排序、希尔排序),选择排序(直接选择排序、堆排序),交换排序(冒泡排序、快速排序),归并排序等,这些排序算法也是我们接下来(以从小到大排序为例)重点要了解的东西。

♫直接插入排序

♪基本思想

直接插入排序是一种简单的插入排序算法,它的基本思想是:从第二个元素开始,每个元素都往前找合适的位置插入,直到所有的记录插入完为止,就能得到一个有序序列

♪算法实现

//直接插入排序(从小到大)
    public void insertSort(int[] arr) {
        //空数组,不用排
        if (arr == null) {
            return;
        }
        //从第二个数开始到最后一个数
        for (int i = 1; i < arr.length; i++) {
            //将待插入的数拿出来
            int tmp = arr[i];
            int j = 0;
            //从后往前找合适的插入位置(待插入值比j位置的值大的位置的后面)
            for (j = i - 1; j >= 0; j--) {
                //判断待插入值是否比j位置的值小
                if (tmp < arr[j]) {
                    //小,位置不对,将该位置的值往后挪,继续找
                    arr[j + 1] = arr[j];
                } else {
                    //大,找到合适位置,退出循环
                    arr[j + 1] = tmp;
                    break;
                }
            }
            //当待插入值最小时,将待插入值插入到第一个位置;当找到合适位置,将待插入值插入合适位置
            arr[j + 1] = tmp;
        }
    }

♪算法稳定性

当相邻的元素比较大小时,若相等,直接插入排序不会改变它们的位置,因此保证了排序前后相等元素的相对位置不变,故直接插入排序是稳定的排序。

♪时间复杂度

最好情况(排序一组有序序列):对n-1个元素进行插入操作,由于每个元素都不需要移动,故时间复杂度为O(n)
最坏情况(排序一组逆序序列):对n-1个元素进行插入操作,每个元素对应的移动次数依次为:1、2、3、....、n-1,等差数列求和得(n-1)(1+n-1)/2=n(n-1)/2=(n^2-n)/2,故算法时间复杂度为O(n^2)

注:数组越稳定,使用直接插入排序时效率越高

♪空间复杂度

只需要一个额外的空间存放待插入值,故空间复杂度为O(1)

♫希尔排序

♪基本思想

我们知道序列越有序,直接插入排序的效率越高,而希尔排序就是先通过局部的直接插入排序将序列变得相对有序后再进行整体的直接插入排序的排序算法,希尔排序也叫作缩小增量排序。希尔排序法的基本思想是:① 先取gap作为第一个增量,把序列根据gap分组。② 所有距离为gap的倍数的记录放在同一个组中,在各组内进行直接插入排序。③ 取第二个缩小的增量gap2,重复上述的分组和排序,直至所取的增量gap=1,即所有记录放在同一组中进行直接插入排序为止。

以gap为元素个数的一半,每次gap的值缩小一半为例:

gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样整体的直接插入排序就会很快。

♪算法实现

这里依然以gap为元素个数的一半,每次gap的值缩小一半为例:

    //希尔排序(从小到大)
    public void shellSort(int[] arr) {
        //可数组,不用排
        if (arr == null) {
            return;
        }
        //增量的大小取数组元素的个数的一半
        int gap = arr.length / 2;
        //增量每次缩小一半,直至为减为1
        for (; gap > 0; gap /= 2) {
            //对数组进行指定增量的直接插入排序
            shell(arr, gap);
        }
    }
    //指定增量的直接插入排序
    public void shell(int[] arr, int gap) {
        //从第一组的第二个元素开始直到整个数组的最后一个元素
        for (int i = gap; i < arr.length; i++) {
            //取出待插入的元素
            int tmp = arr[i];
            int j = 0;
            //从每组待插入元素的位置到该组的第一个元素
            for (j = i - gap; j >= 0; j -= gap) {
                //判断待插入值是否比j位置的值小
                if (tmp < arr[j]) {
                    //小,位置不对,将该位置的值往后挪,继续找
                    arr[j + gap] = arr[j];
                } else {
                    //大,找到合适位置,退出循环
                    arr[j + gap] = tmp;
                    break;
                }
            }
            //当待插入值最小时,将待插入值插入到第一个位置;当找到合适位置,将待插入值插入合适位置
            arr[j + gap] = tmp;
        }
    }

♪算法稳定性

因为希尔排序的是缩小增量的插入排序(无法保证每次分组相同元素都在同一组),故希尔排序是不稳定的排序。

♪时间复杂度

希尔排序的时间复杂度根据gap取法的不同而不同,上述取法的时间复杂度约为O(n^1.25)~O(1.6*n^1.25)之间,其中n为数组中元素的个数。

♪空间复杂度

希尔排序的空间复杂度和直接插入排序一样都是O(1)

♫直接选择排序

♪基本思想

直接选择排序的基本思想是:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

♪算法实现

    //选择排序(从小到大)
    public void selectSort(int[] arr) {
        //空数组,不用排
        if (arr == null) {
            return;
        }
        //从第一个元素开始到倒二个元素
        for (int i = 0; i < arr.length-1; i++) {
            //记录i及其i后面元素的最小值
            int minIndex = i;
            //找出i及其后面最小元素的下标
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[minIndex] > arr[j]) {
                    minIndex = j;
                }
            }
            //将最小元素的位置移到i位置
            if (i != minIndex) {
                int tmp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = tmp;
            }
        }
    }

♪算法稳定性

在选择排序中,如果数组中有两个相等的元素,在选择排序的过程中,可能会先选中后面的元素,导致它们的原始位置交换,这样就破坏了它们在原数组中的相对顺序,故直接选择排序是不稳定的排序。

♪时间复杂度

对n-1个元素进行操作,每个元素对应的比较次数依次为:n-1,n-2,n-3,...,1,等差数列求和得(n-1)(1+n-1)/2=n(n-1)/2=(n^2-n)/2,故算法时间复杂度为O(n^2)

♪空间复杂度

直接选择排序只需要一个额外的常数级别的空间来交换元素,因此直接选择排序的空间复杂度为O(1)

♫堆排序

♪基本思想

堆排序是指利用堆这种数据结构所设计的一种排序算法,它是通过堆来进行选择数据(排升序要建大堆,排降序建小堆),是选择排序的一种。堆排序的基本思想是:

①.将待排序序列构造成一个大/小根堆。②.将其与末尾元素进行交换, 此时末尾就为最大值。③.然后将剩余的个元素重新调整成一个大/小根堆,反复执行上述操作, 便能得到一个有序序列了。

♪算法实现

    //堆排序(从小到大)
    public void heapSort(int[] arr) {
        //空数组,不用排
        if (arr == null) {
            return;
        }
        //创建大根堆
        createBigHeap(arr);
        int end = arr.length - 1;
        while (end > 0) {
            //将堆顶元素和堆的最后一个元素交换
            int tmp = arr[0];
            arr[0] = arr[end];
            arr[end] = tmp;
            //将剩余元素重新调整为大根堆
            shiftDown(arr, 0, end);
            //堆的个数减一
            end--;
        }
    }
    //创建大根堆
    private void createBigHeap(int[] arr) {
        //从最右边的叶子节点开始
        for (int parent = (arr.length - 1 - 1) / 2; parent >= 0; parent--) {
            //向下调整
            shiftDown(arr, parent, arr.length);
        }
    }
    //向下调整
    private void shiftDown(int[] arr, int parent, int len) {
        //child为parent的左孩子(右孩子可能不存在)
        int child = 2 * parent + 1;
        while (child < len) {
            //child为parent的左右孩子节点中的最小节点
            if (child + 1 < len && arr[child] < arr[child + 1]) {
                child = child + 1;
            }
            //大根堆:parent节点应该是parent节点和child节点中最大的那个节点
            if (arr[parent] < arr[child]) {
                int tmp = 0;
                arr[parent] = tmp;
                arr[parent] = arr[child];
                arr[child] = tmp;
                //继续向下调整
                parent = child;
                child = 2 * parent + 1;
            } else {
                //已经是大根堆了,退出循环
                break;
            }
        }
    }

♪算法稳定性

因为在调整堆的过程中,会进行多次元素的交换,这样可能会打破原本相等元素之间的相对位置关系,故堆排序是不稳定的排序。

♪时间复杂度

初始化堆的时间复杂度为O(n),删除操作的时间复杂度为O(logn),每删除一个元素,总元素减一,n-1次的删除操作的时间应该为:log(n)+log(n-1)+…+log(3)+log(2) = log(n!),
又因(n/2)^(n/2) ≤ n ≤ n^n,即 1/4*nlog(n) ≤ n! ≤ nlogn,去掉常数后时间复杂度为O(nlogn),故堆排序的时间复杂度为O(nlogn)

♪空间复杂度

堆排序是一种原地排序算法,所有操作都在原始的数组中进行,故堆排序的空间复杂度为O(1)

♫冒泡排序

♪基本思想

冒泡排序可以看成冒泡的过程,每次排序都会让一个“泡泡”(指一个元素)浮到数组的正确位置。冒泡排序的基本思想是:①从数组的第一个元素开始,比较相邻的两个元素。②如果相邻的两个元素顺序不符合要求(比如升序排序时,前一个数大于后一个数),则交换这两个元素的位置。③继续比较下一个相邻的两个元素,直到数组的末尾。④重复以上步骤,每次比较时都从数组的第一个元素开始,直到数组完全有序为止。

♪算法实现

    //冒泡排序(从小到大)
    public void bubbleSort(int[] arr) {
        //空数组,不用排
        if (arr == null) {
            return;
        }
        //总共需要对对n-1个元素进行比较
        for (int i = 0; i < arr.length - 1; i++) {
            boolean flg = false;
            //每个元素和n-1-i个元素进行比较(i为序列后面已经有序的元素)
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //比较相邻两个元素,大的往后移
                if (arr[j] < arr[j + 1]) {
                    int tmp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = tmp;
                    //进行过交换操作,当前序列仍未有序
                    flg = true;
                }
            }
            //不需要进行交换操作,当前序列已经有序了
            if (flg == false) {
                break;
            }
        }
    }

♪算法稳定性

在冒泡排序中只有前后元素比较大小并进行交换,而相同元素之间是不会交换的,故冒泡排序是一种稳定的排序。

♪时间复杂度

在最坏的情况下,即序列本来就是逆序的情况下,需要进行n-1次遍历,每次遍历需要比较和交换n-i次,因此总的比较和交换次数是n(n-1)/2,故冒泡排序的时间复杂度是O(n^2)

♪空间复杂度

因为冒泡排序只需要一个常量级别的额外空间来交换相邻的元素,故冒泡排序的空间复杂度为O(1)

♫快速排序

♪基本思想

快速排序(Quicksort)是一种高效的排序算法,基于分治的思想,通过不断地将待排序序列划分为独立的两部分,然后对每一部分递归地进行快速排序,最终得到有序序列。 快速排序的基本思想是: 任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

♪算法实现

    //快速排序(从小到大)
    public void quickSort(int[] arr) {
        //空数组,不用排
        if (arr == null) {
            return;
        }
        //对整个数组进行快速排序
        quick(arr, 0, arr.length - 1);
    }
    private void quick(int[] arr, int start, int end) {
        //递归结束条件
        if (end <= start) {
            return;
        }
        //获取基准数的位置
        int pivot = partition(arr, start, end);
        //对基准数的左边进行快速排序
        quick(arr, start, pivot - 1);
        //对基准数的右边进行快速排序
        quick(arr, pivot + 1, end);
    }
其中找基准数的代码根据基准值划分区间的方式不同,算法的实现也就不同,常见的划分区间的方式有:
♩. Hoare法
Hoare法的具体步骤如下:

①.首先选择一个pivot元素,将待排序数组分成左右两部分;

②.然后将左右两部分分别用一个指针来遍历数组;

③.左指针指向左部分的第一个元素,右指针指向右部分的最后一个元素;

④.左指针向右移动,直到找到第一个大于等于pivot的元素;

⑤.右指针向左移动,直到找到第一个小于等于pivot的元素;

⑥.如果左指针和右指针还没有相遇,则将它们所指的元素交换;

⑦.重复步骤4-6,直到左指针和右指针相遇;

⑧.最后将pivot元素放到相遇点的位置上,使得左部分的元素都小于等于pivot,右部分的元素都大于等于pivot;

⑨.递归地对左右两部分进行排序,直到整个数组有序。

代码实现:
    //Hoare法找基准数
    private int partition(int[] arr, int left, int right) {
        int n = left;
        //初始基准
        int pivot = arr[left];
        while (left < right) {
            //从右边开始找比基准数大的
            while (left < right && arr[right] >= pivot) {
                right--;
            }
            //从左边找比基准数小的
            while (left < right && arr[left] <= pivot) {
                left++;
            }
            //左边小的和右边打的交换
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
        }
        //left和right相遇了,将left和初始基准数交换
        int tmp = arr[n];
        arr[n] = arr[left];
        arr[left] = tmp;
        //返回新基准数:left
        return left;
    }
注:只能先从右边找比基准数大的数,如果先从左边找比基准数小的数的话最后和基准数交换的时候会把比基准数大的数换到基准数前面。
♩挖坑法
挖坑法的具体步骤如下:

①.选定一个基准元素,通常选择第一个元素或最后一个元素。

②.将序列分为两部分,一部分为小于基准元素的部分,一部分为大于等于基准元素的部分。

③.在小于基准元素的部分中,从右到左寻找第一个大于等于基准元素的元素,将该元素赋值到基准元素的位置。

④.在大于等于基准元素的部分中,从左到右寻找第一个小于基准元素的元素,将该元素赋值到第3步所挖的坑中。

⑤.重复3、4步,直到左右两部分扫描相遇,然后将基准元素放回该位置。

⑥.递归地对左右两部分进行快速排序

代码实现:

//挖坑法找基准数
private int partition(int[] arr, int left, int right) {
    //记录初始基准数
    int pivot = arr[left];
    while (left < right) {
        //从右边开始找比基准数大的
        while (left < right && arr[right] >= pivot) {
            right--;
        }
        //将右边大的放到left位置
        arr[left] = arr[right];
        //从右边开始找比基准数大的
        while (left < right && arr[left] <= pivot) {
            left++;
        }
        //将左边小的放到left位置
        arr[right] = arr[left];
    }
    //left和right相遇,将初始基准数放到left位置
    arr[left] = pivot;
    //返回新基准数:left
    return left;
}
♩前后指针法
前后指针法的步骤为:

①.选取一个基准数,一般选择第一个数或最后一个数;

②.定义前后指针i和j,分别指向数列的开头和结尾;

③.从后往前找到第一个比基准数小的数,记录其坐标j;

④.从前往后找到第一个比基准数大的数,记录其坐标i;

⑤.如果i < j,则交换i和j的位置,使得比基准数大的数在右侧,比基准数小的数在左侧;

⑥.重复③、④、⑤步骤,直到i >= j,此时前后指针相遇;

⑦.交换基准数和前后指针相遇的位置,即将基准数放入正确的位置;

⑧.将数列分成两个部分,分别对左边和右边的部分重复以上步骤,直到整个数列有序。

代码实现:

    //前后指针法找基准数
    private int partition(int[] arr, int left, int right) {
        int prev = left, cur = left+1;
        while (cur <= right) {
            while (arr[cur] < arr[left] && arr[++prev] != arr[cur]) {
                int tmp = arr[cur];
                arr[cur] = arr[prev];
                arr[prev] = tmp;
            }
            cur++;
        }
        int tmp = arr[left];
        arr[left] = arr[prev];
        arr[prev] = tmp;
        return prev;
    }

♪算法的优化

♩三数取中

当取到的基准数为最大或最小时,快速排序的效率是比较低的,故我们可以通过选取最左边,中间,最右边三个数的中间值降低取到的基准数最大或最小的情况。

    private void quick(int[] arr, int start, int end) {
        //递归结束条件
        if (end <= start) {
            return;
        }
        //找到下标为start、end、(start+end)/2中的数中第二大的数的小标
        int MidIndex = findMidValOfIndex(arr, start, end);
        //确保三个数中第二大的在start位置
        if (MidIndex != start) {
            int tmp = arr[start];
            arr[start] = arr[MidIndex];
            arr[MidIndex] = tmp;
        }
        //获取下一个基准数的位置
        int pivot = partition(arr, start, end);
        //对基准数的左边进行快速排序
        quick(arr, start, pivot - 1);
        //对基准数的右边进行快速排序
        quick(arr, pivot + 1, end);
    }
    //找到下标为start、end、(start+end)/2中的数中第二大的数的小标
    private int findMidValOfIndex(int[] array, int start, int end) {
        int midIndex = (start+end) / 2;
        if(array[start] < array[end]) {
            if(array[midIndex] < array[start]) {
                return start;
            }else if(array[midIndex] > array[end]) {
                return end;
            }else {
                return midIndex;
            }
        }else {
            if(array[midIndex] > array[start]) {
                return start;
            }else if(array[midIndex] < array[end]) {
                return end;
            }else {
                return midIndex;
            }
        }
    }

♩小区间快排

快速排序越排是越有序的,故当待排序序列长度较小时,插入排序的性能可能比快速排序更优。因此可以在递归深度达到一定阈值时,使用插入排序来处理子序列。

    private void quick(int[] arr, int start, int end) {
        //递归结束条件
        if (end <= start) {
            return;
        }
        //递归到小区间时,用直接插入排序
        if (end - start + 1 <= 15) {
            insertSort(arr, start, end);
            return;
        }
        //找到下标为start、end、(start+end)/2中的数中第二大的数的小标
        int MidIndex = findMidValOfIndex(arr, start, end);
        //确保三个数中第二大的在start位置
        if (MidIndex != start) {
            int tmp = arr[start];
            arr[start] = arr[MidIndex];
            arr[MidIndex] = tmp;
        }
        //获取下一个基准数的位置
        int pivot = partition(arr, start, end);
        //对基准数的左边进行快速排序
        quick(arr, start, pivot - 1);
        //对基准数的右边进行快速排序
        quick(arr, pivot + 1, end);
    }
    //直接插入排序
    private void insertSort(int[] arr, int left, int right) {
        for (int i = left+1; i <= right; i++) {
            int tmp = arr[i];
            int j = 0;
            for (j = i-1; j >= left; j--) {
                if (arr[j] > tmp) {
                    arr[j+1] = arr[j];
                } else {
                    break;
                }
            }
            arr[j+1] = tmp;
        }
    }

♪算法稳定性

在快速排序中,如果两个元素的值相同,它们在排序后可能会交换位置,因此快速排序是不稳定的排序。

♪时间复杂度

快速排序的时间复杂度取决于分割点选择的方法,如果每次选择中位数作为分割点,则最坏时间复杂度为O(n^2),平均时间复杂度为O(n log n);如果分割点随机选择,则最坏时间复杂度为O(n^2)的概率很小,平均时间复杂度为O(n log n)。因此,快速排序的平均时间复杂度为O(n log n),最坏时间复杂度为O(n^2)

♪空间复杂度

快速排序的空间复杂度为 O(nlogn),其中 n 为排序的元素个数。这是因为快速排序是一种原地排序算法,它只需要将排序过程中所需的一些暂存空间复用,不会额外占用大量的内存空间。而在最坏情况下,快速排序的空间复杂度可能会退化为 O(n),但这种情况非常罕见。

♫归并排序

♪基本思想

归并排序是一种分治算法,是将一个大的问题分解成若干个小问题来解决,然后将这些小问题的解合并起来,得到原始问题的解。归并排序的基本思想是:先将待排序的序列分成两个部分,直到每个部分只有一个元素为止,再将两个单元素部分归并成一个有序序列,直到所有序列都合并成一个有序序列为止。

♪算法实现

//归并排序(从小到大)
    public void mergeSort(int[] arr) {
        mergeSortChild(arr, 0, arr.length-1);
    }
    public void mergeSortChild(int[] arr, int left, int right) {
        if (left <= right) {
            return;
        }
        int mid = (left+right) / 2;
        mergeSortChild(arr, left, mid);
        mergeSortChild(arr, mid+1, right);
        merge(arr,left,mid,right);
    }

    private static void merge(int[] arr,int left,int mid,int right) {
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int[] tmpArr = new int[right-left+1];
        int k = 0;//表示tmpArr 的下标
        while (s1 <= e1  && s2 <= e2) {
            if(arr[s1] <= arr[s2]) {
                tmpArr[k++] = arr[s1++];
            }else{
                tmpArr[k++] = arr[s2++];
            }
        }
        while (s1 <= e1) {
            tmpArr[k++] = arr[s1++];
        }
        while (s2 <= e2) {
            tmpArr[k++] = arr[s2++];
        }
        //tmpArr当中 的数据 是right  left 之间有序的数据
        for (int i = 0; i < k; i++) {
            arr[i+left] = tmpArr[i];
        }
    }

♪算法的稳定性

归并排序在合并两个有序子序列的时候,如果两个元素的大小相同,那么在归并的结果中,左边的元素肯定会先被放入序列中,也就是说它们的相对顺序没有改变。故归并排序是一种稳定的排序。

♪时间复杂度

归并排序在排序过程中,先将待排序的序列递归地拆分为两个子序列,每次拆分会将序列长度缩小一半。然后将两个子序列合并成一个有序的序列,合并操作的时间复杂度为O(n)。整个排序过程中,序列被拆分成了logn级别的子序列,每个子序列都要进行一次合并操作,所以时间复杂度为O(nlogn)

♪空间复杂度

在归并排序中,需要创建一个额外的数组来保存排序的结果,在归并过程中,需要不断地合并两个有序数组,因此需要创建一个长度为n的数组来保存排序后的结果。另外,在归并排序的递归过程中,需要创建一个长度为n的临时数组来保存每次递归的中间结果,所以归并排序的空间复杂度为O(n)

♫排序算法的比较

排序算法最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度稳定性
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
插入排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
希尔排序O(n)O(n^2)O(n^1.3)O(1)不稳定
堆排序O(n*logn)O(n*logn)O(n*logn)O(1)不稳定
快速排序O(n*logn)O(n^2)O(n*logn)O(logn)~O(n)不稳定
归并排序O(n*logn)O(n*logn)O(n*logn)O(n)稳定

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1035655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于51单片机简易计算器仿真设计(proteus仿真+程序+原理图+PCB+设计报告+讲解视频)

基于51单片机简易计算器仿真设计&#xff08;proteus仿真程序原理图PCB设计报告讲解视频&#xff09; 讲解视频1.1 功能要求1.2 仿真图&#xff1a;1.3 原理图&#xff1a;1.4 PCB&#xff1a;1.5 源程序&#xff1a;1.6设计报告&#xff1a;资料清单&&下载链接&#x…

C/C++统计满足条件的4位数个数 2023年5月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C统计满足条件的4位数个数 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C统计满足条件的4位数个数 2019年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给定若干…

搭建安信可小安派Windows 开发环境

搭建小安派Windows 开发环境 Ai-Pi-Eyes 系列是安信可开源团队专门为Ai-M61-32S设计的开发板&#xff0c;支持WiFi6、BLE5.3。所搭载的Ai-M61-32S 模组具有丰富的外设接口&#xff0c;具体包括 DVP、MJPEG、Dispaly、AudioCodec、USB2.0、SDU、以太网 (EMAC)、SD/MMC(SDH)、SP…

硬件知识积累 网口接口 百兆,千兆,万兆 接口介绍与定义 (RJ45 --简单介绍)

1. 百兆网口 1.1百兆网的定义 百兆网的意思是是100Mb/S&#xff0c;中文叫做100兆位/秒。 1.2百兆网口的常用连接器 1.1.1 一般百兆网口的连接器一般是RJ45 下面是 实物图&#xff0c; 原理图&#xff0c;封装图。 1.3 百兆网口连接线的介绍 1.3.1 百兆需要使用的线的定义 百…

嵌入式开发笔记:STM32的外设GPIO知识学习

GPIO简介&#xff1a; • GPIO &#xff08; General Purpose Input Output &#xff09;通用输入输出口 • 可配置为 8 种输入输出模式 • 引脚电平&#xff1a; 0V~3.3V &#xff0c;部分引脚可容忍 5V &#xff08;如舵机和驱动直流电机&#xff09; • 输出模式下可控制端口…

《从菜鸟到大师之路 MySQL 篇》

《从菜鸟到大师之路 MySQL 篇》 数据库是什么 数据库管理系统&#xff0c;简称为DBMS&#xff08;Database Management System&#xff09;&#xff0c;是用来存储数据的管理系统。 DBMS 的重要性 无法多人共享数据 无法提供操作大量数据所需的格式 实现读取自动化需要编程…

《软件方法(下)》第8章2023版连载(02)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.1.5 重视分析工作流 分析&#xff0c;就是从核心域的视角构思系统的内部机理。 在现在的很多软件组织中&#xff0c;分析工作流的技能被严重忽视。很多开发人员上手就直接编码&…

92 # express 中的中间件的实现

上一节实现 express 的优化处理&#xff0c;这一节来实现 express 的中间件 中间件的特点&#xff1a; 可以决定是否向下执行可以拓展属性和方法可以权限校验中间件的放置顺序在路由之前 中间件基于路由&#xff0c;只针对路径拦截&#xff0c;下面是中间件的匹配规则&#…

HTTP、TCP、SOCKET三者之间区别和原理

7层网络模型 网络在世界范围内实现互联的标准框架 7层为理想模型&#xff0c;一般实际运用没有7层 详细内容 HTTP属于7层应用层 BSD socket属于5层会话层 TCP/IP属于4成传输层 TCP/IP协议 三次握手 笔者解析&#xff1a; 第一次握手&#xff1a;实现第一步需要客户端主动…

【WSL】下载appx包将WSL装在非系统盘

装系统软件这事&#xff0c;主打一个小强精神 首先&#xff0c;准备好一个微软官方提供的安装包。下载链接&#xff1a;https://learn.microsoft.com/en-us/windows/wsl/install-manual#downloading-distributions 然后&#xff0c;剩下步骤在这个问题讨论中已经说明了&#xf…

【苹果】SpringBoot监听Iphone15邮件提醒,Selenium+Python自动化抢购脚本

前言 &#x1f34a;缘由 Iphone15来了&#xff0c;两年之约你还记得吗&#xff1f; 两年前&#xff0c;与特别的人有一个特别的约定。虽物是人非&#xff0c;但思念仍在。 遂整合之前iphone13及iphone14的相关抢购代码&#xff0c;完成一个SpringBoot监听Iphone15有货邮件提…

PHP后台实现微信小程序登录

微信小程序官方给了十分详细的登陆时序图&#xff0c;当然为了安全着想&#xff0c;应该加上签名加密。 微信小程序端 1).调用wx.login获取 code 。 2).调用wx.getUserInfo获取签名所需的 rawData , signatrue , encryptData 。 3).发起请求将获取的数据发送的后台。 login: …

Nginx 解决内容安全策略CSP(Content-Security-Policy)配置方式

1、修改 nginx 配置文件 在nginx.conf 配置文件中&#xff0c;增加如下配置内容&#xff1a; add_header Content-Security-Policy "default-src self localhost:8080 unsafe-inline unsafe-eval blob: data: ;";修改后效果如下&#xff1a; 2、重启 nginx 服务 …

【CFD小工坊】模型网格(三角形网格)

【CFD小工坊】模型网格&#xff08;三角形网格&#xff09; 前言网格几何网格编号编程实现数据读入网格数据构建 本系列博文的是我学习二维浅水方程理论&#xff0c;直至编译一个实用的二维浅水流动模型的过程。&#xff08;上一篇Blog回顾&#xff09; 前言 本二维浅水模型将…

Docker部署Nginx+FastDFS插件

文章目录 一、部署FastDFS二、部署Nginx(带FastDFS插件)三、FastDFS上传文件Nginx访问验证 一、部署FastDFS 1、准备工作 docker pull qinziteng/fastdfs:5.05 Pwd"/data/software/fastdfs" mkdir ${Pwd}/{storage,tracker} -p2、创建TEST容器&#xff0c;将fastdf…

uniapp中vue3使用uni.createSelectorQuery().in(this)报错

因为VUE3中使用setup没有this作用域&#xff0c;所以报错 解决办法&#xff1a;使用getCurrentInstance()方法获取组件实例 import { getCurrentInstance } from vue;const instance getCurrentInstance(); // 获取组件实例 const DOMArr uni.createSelectorQuery().in(ins…

【C#】.Net基础语法二

目录 一、字符串(String) 【1.1】字符串创建和使用 【1.2】字符串其他方法 【1.3】字符串格式化的扩展方法 【1.4】字符串空值和空对象比较 【1.5】字符串中的转移字符 【1.6】大写的String和小写的string 【1.7】StringBuilder类的重要性 二、数组(Array) 【2.1】声…

大数据的崭露头角:数据湖与数据仓库的融合之道

文章目录 数据湖与数据仓库的基本概念数据湖&#xff08;Data Lake&#xff09;数据仓库&#xff08;Data Warehouse&#xff09; 数据湖和数据仓库的优势和劣势数据湖的优势数据湖的劣势数据仓库的优势数据仓库的劣势 数据湖与数据仓库的融合之道1. 数据分类和标记2. 元数据管…

RabbitMQ快速入门——消费者

public class Consumer_HelloWorld {public static void main(String[] args) throws IOException, TimeoutException {//1.创建连接工厂ConnectionFactory factory new ConnectionFactory();//2.设置参数factory.setHost("172.16.98.133"); ip 默认值 localhostfac…

Vue3-Vue3生命周期、自定义hook函数、toRef与toRefs、其他组合式API、组合式API的优势、Vue3新的组件和功能

Vue3&#xff08;2&#xff09; 更多Vue.js知识请点击——Vue.js &#x1f954;&#xff1a;有的山长满荆棘&#xff0c;有的山全是野兽&#xff0c;所以你应该是自己的那座山 文章目录 Vue3&#xff08;2&#xff09;一、Vue3生命周期二、自定义hook函数三、toRef与toRefs四、…