首页 > 编程语言 >时间复杂度为 O(n^2) 的排序算法

时间复杂度为 O(n^2) 的排序算法

时间:2024-10-17 10:50:38浏览次数:7  
标签:nums int 插入排序 算法 排序 复杂度

作者:京东保险 王奕龙

对于小规模数据,我们可以选用时间复杂度为 O(n2) 的排序算法。因为时间复杂度并不代表实际代码的执行时间,它省去了低阶、系数和常数,仅代表的增长趋势,所以在小规模数据情况下, O(n2) 的排序算法可能会比 O(nlogn) 的排序算法执行效率高。不过随着数据规模增大, O(nlogn) 的排序算法是不二选择。本篇我们主要对 O(n2) 的排序算法进行介绍,在介绍之前,我们先了解一下算法特性:

  • 算法特性:

    • 稳定性:经排序后,若等值元素之间的相对位置不变则为稳定排序算法,否则为不稳定排序算法

    • 原地排序:是否借助额外辅助空间

    • 自适应性: 自适应性排序受输入数据的影响,即最佳/平均/最差时间复杂度不等,而非自适应排序时间复杂度恒定

本篇我们将着重介绍插入排序,选择排序和冒泡排序了解即可。

插入排序

插入排序的工作方式像 整理手中的扑克牌一样,即不断地将每一张牌插入到其他已经有序的牌中适当的位置。

插入排序的当前索引元素左侧的所有元素都是有序的:若当前索引为 i,则 [0, i - 1] 区间内的元素始终有序,这种性质被称为 循环不变式,即在第一次迭代、迭代过程中和迭代结束时,这种性质始终保持不变。

不过,这些有序元素的索引位置暂时不能确定,因为它们可能需要为更小的元素腾出空间而向右移动。插入排序的代码实现如下:

    private void sort(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            int base = nums[i];

            int j = i - 1;
            while (j >= 0 && nums[j] > base) {
                nums[j + 1] = nums[j--];
            }
            nums[j + 1] = base;
        }
    }

它的实现逻辑是取未排序区间中的某个元素为基准数 base,将 base 与其左侧已排序区间元素依次比较大小,并"插入"到正确位置。插入排序对 部分有序(数组中每个元素距离它的最终位置都不远或数组中只有几个元素的位置不正确等情况)的数组排序效率很高。事实上,当逆序很少或数据量不大(n2和nlogn比较接近)时,插入排序可能比其他任何排序算法都要快,这也是一些编程语言的内置排序算法在针对小数据量数据排序时选择使用插入排序的原因。

算法特性:

  • 空间复杂度:O(1)

  • 原地排序

  • 稳定排序

  • 自适应排序:当数组为升序时,时间复杂度为 O(n);当数组为降序时,时间复杂度为 O(n2)

希尔排序

插入排序对于大规模乱序数组排序很慢,因为它只会交换相邻的元素,所以元素只能一步步地从一端移动到另一端,如果最小的元素恰好在数组的最右端,要将它移动到正确的位置需要移动 N - 1 次。

希尔排序是基于插入排序改进的排序算法,它可以交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。它的思想是使数组中间隔为 h 的元素有序(h 有序数组),如下图为间隔为 4 的有序数组:

希尔排序.jpg

排序之初 h 较大,这样我们能将较小的元素尽可能移动到靠近左端的位置,为实现更小的 h 有序创造便利,最后一次循环时 h 为 1,便是我们熟悉的插入排序。这就是希尔排序的过程,代码实现如下:

    private void sort(int[] nums) {
        int N = nums.length;
        int h = 1;
        while (h < N / 3) {
            h = 3 * h + 1;
        }

        while (h >= 1) {
            for (int i = h; i < N; i++) {
                int base = nums[i];

                int j = i - h;
                while (j >= 0 && nums[j] > base) {
                    nums[j + h] = nums[j];
                    j -= h;
                }
                nums[j + h] = base;
            }

            h /= 3;
        }
    }

希尔排序更高效的原因是它权衡了子数组的规模和有序性,它也可以用于大型数组。排序之初,各个子数组都很短,排序之后子数组都是部分有序的,这两种情况都很适合插入排序。


选择排序

选择排序的实现非常简单:每次选择未排序数组中的最小值,将其放到已排序区间的末尾,代码实现如下:

    private void sort(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            int min = i;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[min]) {
                    min = j;
                }
            }
            swap(nums, i, min);
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

算法特性:

  • 空间复杂度:O(1)

  • 原地排序

  • 非稳定排序:会改变等值元素之间的相对位置

  • 非自适应排序:最好/平均/最坏时间复杂度均为 O(n2)

冒泡排序

冒泡排序通过 连续地比较与交换相邻元素实现排序,每轮循环会将未被排序区间内的最大值移动到数组的最右端,这个过程就像是气泡从底部升到顶部一样,代码实现如下:

    public void sort(int[] nums) {
        for (int i = nums.length - 1; i > 0; i--) {
            // 没有发生元素交换的标志位
            boolean flag = true;
            for (int j = 0; j < i; j++) {
                if (nums[j] > nums[j + 1]) {
                    swap(nums, j, j + 1);
                    flag = false;
                }
            }

            if (flag) {
                break;
            }
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

算法特性:

  • 空间复杂度:O(1)

  • 原地排序

  • 稳定排序

  • 自适应排序:经过优化后最佳时间复杂度为 O(n)


巨人的肩膀

  • 《算法导论 第三版》第 2.1 章

  • 《算法 第四版》第 2.1 章

  • 《Hello 算法》第 11 章

  • 排序算法-希尔排序

标签:nums,int,插入排序,算法,排序,复杂度
From: https://www.cnblogs.com/Jcloud/p/18471580

相关文章

  • Collections.sort多个字段排序
    //生效日期、操作时间倒序、机型组升序privatevoidsort(List<IntpathcostAreaGroupstVO>data){Comparator<Object>com=Collator.getInstance(java.util.Locale.CHINA);Collections.sort(data,newComparator<IntpathcostAreaGroupstVO>(){@Override......
  • 算法与数据结构——堆排序
    堆排序堆排序(heapsort)是一种基于堆数据结构实现的高效排序算法。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。输入数组并建立小顶堆,此时最小元素位于堆顶。不断执行出堆操作,依次记录出堆元素,即可得到从小到大排序的序列。以上方法虽然可行,但需借助一......
  • go 基于推特雪花算法生成定长id
    基于推特雪花算法生成定长id,属于int64类型。1BitUnused|41BitTimestamp|10BitNodeID|12BitSequenceID1bit未使用,默认是0。41bit存储毫秒级时间戳,当前时间与Nov04201001:42:54UTC的时间差。10bit存储节点的id,最多支持1024个。12bit自增id,同1个节点在1秒内最多......
  • 206号资源-源程序:2024年新智能优化算法-鹦鹉优化算法---------已提供下载资源
    ......
  • 数模创新算法篇 | 基于CEEMDAN分解与LSTM模型的电力负荷预测
    目录 废话不多说,直接上目录问题背景与理论1.长短期记忆网络(LSTM)理论2.CEEMDAN分解理论3.LSTM与CEEMDAN结合的优势4.应用场景与前景Python代码实操导入库和准备数据备注定义数据整理函数定义LSTM模型构建函数数据处理和模型训练评估模型性能绘制预测结果图......
  • 必学的排序算法——冒泡排序
    目录前言一、什么是冒泡排序二、冒泡排序的的基本步骤三、冒泡排序的特点四、冒泡排序算法图解五、经典例题1.合并两个有序数组代码题解2.元素计数代码题解3.最后一块石头的重量代码题解六、结语前言冒泡排序算法是必须掌握的一种基础算法,在一些比较出名的竞赛可......
  • 选择排序,插入排序,快速排序的java简单实现
    代码功能以下Java代码包含了三个排序算法的实现:选择排序(SelectionSort):通过不断选择剩余元素中的最小值来排序数组。插入排序(InsertionSort):通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。快速排序(QuickSort):使用分治法,通过一个基准值......
  • C++算法练习-day1——704.二分查找
    题目来源:.-力扣(LeetCode)题目思路分析二分查找是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从......
  • 路径规划——RRT、RRT*、RRT-Connect算法说明
    1.RRT伪代码RRT的目标是在状态空间中随机采样并扩展树结构,直到找到连接起点和终点的路径。RRT(Rapidly-exploringRandomTree,快速随机扩展树)是一种用于解决运动规划问题的采样算法。它通过随机采样的方式,逐步构建一棵树,直到找到一条从起始状态到目标状态的路径。RRT算法的......
  • 使用MySQL之排序检索数据
    排序数据子句(clause):SQL语句由子句构成,有些子句是必需的,而有的是可选的。一个子句通常由一个关键字和所提供的数据组成。子句的例子有SELECT语句的FROM子句等。为了明确地排序用SELECT语句检索出的数据,可使用ORDERBY子句。ORDERBY子句取一个或多个列的名字,据此对输出进行排序......