Java排序算法总结与复杂度分析_java的复杂度都有哪些值-程序员宅基地

技术标签: 算法  java  数据结构与算法  排序算法  

前言

时间复杂度

概念

时间复杂度简单的说就是一个程序运行所消耗的时间,叫做时间复杂度,我们无法目测一个程序具体的时间复杂度,但是我们可以估计大概的时间复杂度。一段好的代码的就根据算法的时间复杂度,即使在大量数据下也能保持高效的运行速率,这也是我们学习算法的必要性。

时间复杂度表示形式

一般用O()来表示算法的时间复杂度,我们叫做大O记法。

时间复杂度规则
  • 用常数1取代运行时间中的所有的加法常数。比如,一个程序中有十条输出语句,我们不会记成O(10),而是用O(1)来表示。

  • 如果最高阶项不是1,那么去掉最高阶阶项,因为我们认为数字在后期影响不大。如O(2n),则时间复杂度应该为O(n)。

  • 只保留最高阶项,如O(3n^2 +6n+2),则时间复杂度为O(n^2)。

常见的时间复杂度排序

O(1)< O(log2(n))< O(n)< O(nlog2(n)< O(n^2)< O(n^3)< O(2^n)< O(n!)< O(n^n)

空间复杂度

简单的说就是程序运行所需要的空间。写代码我们可以用时间换空间,也可以用空间换时间。加大空间消耗来换取运行时间的缩短加大时间的消耗换取空间,我们一般选择空间换时间。一般说复杂度是指时间复杂度。

更多请参考:【数据结构和算法】时间复杂度和空间复杂度

递归排序时间复杂度估算公式

master公式的使用

T(N) = a*T(N/b) + O(N^d)

N:父问题的样本量;
a:子问题发生的次数(父问题被拆分成了几个子问题,不需要考虑递归调用,只考虑单层的父子关系);
b:被拆成子问题,子问题的样本量(子问题所需要处理的样本量),比如 N 被拆分成两半,所以子问题样本量为 N/2;
O(N^d):剩余操作的时间复杂度,除去调用子过程之外,剩下问题所需要的代价[常规操作则为 O(1)];

a表示

  • log(b,a) > d -> 复杂度为O(N^log(b,a))
  • log(b,a) = d -> 复杂度为O(N^d * logN)
  • log(b,a) < d -> 复杂度为O(N^d)

对数器

概念

存在一个想要测算法a时,可以使其结果集和另一个实现复杂度不好但是容易实现的方法b的结果集进行比较,由此检验算法正确与否。可以控制测试次数,测试样本,不依赖于线上测试平台。

测试步骤
  • 实现一个随机样本产生器;

  • 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样;

  • 如果有随机样本使得比对结果不一致,打印样本进行人工干预,对方法a或方法b进行修改;

  • 当样本数量很多时比对测试依然正确,则可以确定方法a已经正确;

排序算法

冒泡排序

思路

将第1个元素和第2个元素进行比较,若为逆序则将两个元素交换,然后比较第2个元素和第3个元素。依次类推,直至第 n-1个元素和第 n个元素进行比较为止。上述过程称为第一趟冒泡排序,其结果使最大值元素被放置在最后一个位置(第 n个位置)。

然后进行第二趟冒泡排序,对前 n-1个元素进行同样操作,其结果是使第二大元素被放置在倒数第二个位置上(第 n-1个位置);

依次如此操作,直到最小元素放置到第1个位置,完成排序操作;

代码实现
    private static void bubbleSort(int[] nums) {
    
        if (nums == null || nums.length == 1) {
    
            return;
        }
        /**
         * 第一层指遍历范围 第一次冒泡遍历n-1次,后续n-2次...直到1次
         */
        for (int end = nums.length - 1; end > 0; end--) {
    
            /**
             * 第二层进行数据交换
             */
            for (int i = 0; i < end; i++) {
    
                if (nums[i] > nums[i + 1]) {
    
                    int temp = nums[i];
                    nums[i] = nums[i + 1];
                    nums[i + 1] = temp;
                }

            }
        }
    }
复杂度分析

时间复杂度O(N^2),额外空间复杂度O(1)

选择排序

思路

先在[0~n-1]下标范围内找到最小值放到0位置上;
再在[1~n-1]下标范围内找到最小值放到1位置上;
依次如此操作,直到最后一个最小值【最大值】放在n-1位置上,完成排序操作;

代码实现
   private static void selectSort(int[] nums) {
    
        if (nums == null || nums.length <2) {
    
            return;
        }

        for (int i = 0; i < nums.length - 1; i++) {
    
            int minIndex = i;
            for (int j = i + 1; j < nums.length; j++) {
    
                minIndex = nums[j] < nums[minIndex] ? j : minIndex;
            }
            /**
             * 交换i和minIndex位置的数
             */
            int temp = nums[i];
            nums[i] = nums[minIndex];
            nums[minIndex] = temp;
        }

    }
复杂度分析

时间复杂度O(N^2),额外空间复杂度O(1)

插入排序

思路

下标第0位置元素默认已排好序,当前排好序范围[0~0];
下标第1位置元素和第0位置元素比较大小,若小于第0位置元素则交换位置,排好序范围[0~1];
下标第2位置元素和第1位置元素比较大小,若不小于,则不交换位置,若小于第1位置元素,则交互位置,继续和0位置比较,若仍小于第0位置元素,继续交换位置,最终排好序范围[0~2];
依次如此操作,直到完成最终排序;

代码实现
    private static void insertSort(int[] nums) {
    
        if (nums == null || nums.length <2) {
    
            return;
        }
        for (int i = 1; i < nums.length; i++) {
    
        	//如果j位置元素>j+1位置元素,不断交换位置,直到比较到0位置
            for (int j = i - 1; j >= 0 && nums[j] > nums[j + 1]; j--) {
    
                int temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
复杂度分析

时间复杂度O(N^2),额外空间复杂度O(1)

归并排序

思路
  1. 每次将数据划分成两部分,并分别对两部分进行排序;
  2. 每次合并排序好的两部分,依次移动两部分元素下标,找到最小值,依次放置到合并数组中,经过多次合并数组,最终完成排序操作;
代码实现
 private static void mergeSort(int[] nums) {
    
        if (nums == null || nums.length <2) {
    
            return;
        }
        mergeSort(nums, 0, nums.length - 1);
    }


    private static void mergeSort(int[] nums, int l, int r) {
    
        if (l == r) {
    
            return;
        }
        int mid = l + (r - l) / 2;
        //对前半部分进行排序
        mergeSort(nums, l, mid);
        //对后半部分进行排序
        mergeSort(nums, mid + 1, r);
        //合并两者排序
        merge(nums, l, mid, r);
    }

    private static void merge(int[] nums, int l, int mid, int r) {
    
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= r) {
    
            //谁小放谁,并移动对应数组位置
            help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++];
        }
        while (p1 <= mid) {
    
            //说明p2已遍历完毕,后续位置填充p1没遍历完的元素
            help[i++] = nums[p1++];
        }
        while (p2 <= r) {
    
            //说明p1已遍历完毕,后续位置填充p2没遍历完的元素
            help[i++] = nums[p2++];
        }
        for (i = 0; i < help.length; i++) {
    
            nums[l + i] = help[i];
        }
    }

复杂度分析

时间复杂度代入递归master公式 T(N) = 2T(N/2)+O(N) ,即
时间复杂度O(N*logN),额外空间复杂度O(N)

快速排序

思路
  1. 从给定数组中选择一个数作为比较值target,将数组中的元素不断与target进行比较交换,从而将数组整体划分成三个部分【<target=target;>target】,并记录=target部分的起始下标和结束下标
  2. 根据步骤1的下标分别对<target>target部分继续上述划分操作,最终完成整个数组排序;
代码实现
  /**
     * 快速排序
     *
     * @param aar 目标数组
     * @param l   需要排序的起始位置,传0表示从头开始
     * @param r   需要排序的结束为止,传arr.length-1表示结束位置
     */
    private static void quickSort(int[] aar, int l, int r) {
    
        if (aar == null || aar.length < 2) {
    
            return;
        }
        if (l < r) {
    
            //1.先随机选择数组中的一个数,交换到数组最后一个位置,作为比较交换的target,这样做为了概率处理,避免特定情况(比如数组是逆序)造成的多余划分问题
            swap(aar, r, (int) (l + (r - l + 1) * Math.random()));
            //2.在l~r下标范围内,以arr[r]作为target,划分出三部分区域[<target, =target ,>target],并返回 =target范围的第一个下标和最后一个下标数组
            int[] p = quickSortPartition(aar, l, r);
            //3.递归对<target范围排序
            quickSort(aar, l, p[0] - 1);
            //3.递归对>target范围排序
            quickSort(aar, p[1] + 1, r);
        }
    }

 private static int[] quickSortPartition(int[] aar, int l, int r) {
    
        //取数组最后一个作为比较目标值
        int target = aar[r];
        //定义小于target的数组下标
        int less = l - 1;
        //定义大于target的数组下标
        int more = r + 1;
        while (l < more) {
    
            if (aar[l] < target) {
    
                //小于目标值,划分到小于区域
                swap(aar, ++less, l++);
            } else if (aar[l] > target) {
    
                //大于目标值,划分到大于区域
                swap(aar, --more, l);
            } else {
    
                //相等时,仅仅移动l
                l++;
            }
        }

        return new int[]{
    less + 1, more - 1};
    }
复杂度分析

时间复杂度O(N*logN),额外空间复杂度O(logN)

堆排序

相关概念【更多相关概念参考通俗易懂,什么是二叉堆?
最大堆

根结点的值是所有堆结点值中最大者,且每个结点的值都比其孩子的值大。

最小堆

根结点的值是所有堆结点值中最小者,且每个结点的值都比其孩子的值小。

思路
  1. 将数组数据排列成最大二叉堆结构,保证数组下标0位置为数组最大元素;
  2. 交换数组下标0与length-1位置元素,即把最大数下沉到末尾位置;
  3. 去除末尾位置元素,对剩余数组元素找最大值,交换到0位置;
  4. 不断进行步骤2和步骤3,最终完成整体数组排序;
代码实现
/**
     * 堆排序
     *
     * @param arr 需要排序的目标数组
     */
    private static void heapSort(int[] arr) {
    
        if (arr == null || arr.length < 2) {
    
            return;
        }
        for (int i = 0; i < arr.length; i++) {
    
            //构造一个最大堆
            heapInsert(arr, i);
        }
        int size = arr.length;
        swap(arr, 0, --size);
        while (size > 0) {
    
        	//将最大值交换到0位置
            heapify(arr, 0, size);
            //下沉最大值到末尾
            swap(arr, 0, --size);
        }
    }

    private static void heapify(int[] arr, int index, int size) {
    
        //index节点对应的左孩子节点
        int left = index * 2 + 1;
        //只要left节点没有越界
        while (left < size) {
    
            //index节点对应的右孩子节点
            int right = left + 1;
            //右孩子节点 如果没越界 取左右节点最大的下标即为maxIndex
            int maxIndex = right < size && arr[right] > arr[left] ? right : left;
            //再找出maxIndex下标和index下标的最大值对应的下标
            maxIndex = arr[maxIndex] > arr[index] ? maxIndex : index;
            //如果就是index节点,则退出while循环
            if (maxIndex == index) {
    
                break;
            }
            //交换index和maxIndex下标的值
            swap(arr, index, maxIndex);
            //将maxIndex赋值给index,继续往下找符合条件的下标进行交换
            index = maxIndex;
            left = index * 2 + 1;
        }
    }

    /**
     * 基于传入的数组构造一个最大堆
     *
     * @param arr
     * @param index 插入堆的数组下标
     */
    private static void heapInsert(int[] arr, int index) {
    
        //只要i位置元素大于它的根节点,就交换他两的位置,直到根节点(即数组下标为0位置)为最大值
        while (arr[index] > arr[(index - 1) / 2]) {
    
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }
复杂度分析

时间复杂度O(N*logN),额外空间复杂度O(1)

算法案例

逆序对问题

题目描述

剑指 Offer 51. 数组中的逆序对;
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

根据归并排序思路:

 public static int reversePairs(int[] nums) {
    
        if (nums == null || nums.length < 2) {
    
            return 0;
        }
        return reversePairs(nums, 0, nums.length - 1);

    }

    private static int reversePairs(int[] nums, int l, int r) {
    
        if (l == r) {
    
            return 0;
        }
        int mid = l + (r - l) / 2;
        return reversePairs(nums, l, mid) + reversePairs(nums, mid + 1, r) + mergeReversePairs(nums, l, mid, r);
    }

    private static int mergeReversePairs(int[] nums, int l, int mid, int r) {
    
        int i = 0;
        int[] help = new int[r - l + 1];
        int p1 = l;
        int p2 = mid + 1;
        int res = 0;
        while (p1 <= mid && p2 <= r) {
    
            if (nums[p1] > nums[p2]) {
     //如果左部分比右部分大,则逆序对数量应加上p2到右部分总长度
                res += r - p2 + 1;
                help[i++] = nums[p1++];
            } else {
    
                help[i++] = nums[p2++]; 
            }
        }

        while (p1 <= mid) {
    
            help[i++] = nums[p1++];
        }

        while (p2 <= r) {
    
            help[i++] = nums[p2++];
        }

        for (i = 0; i < help.length; i++) {
    
            nums[l + i] = help[i];
        }
        return res;

    }

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/a734474820/article/details/131088825

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文