Java实现第一章、初识数据结构和算法

简介: 算法定义中,提到了指令,指令能被人或机器等计算装置执行。它可以是计算机指令,也可以是我们平时的语言文字。为了解决某个或某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定的功能,这就是算法了。定义:在计算机科学领域,数据结构是一种数据组织、管理和存储格式,通常被选择用来高效访问数据,数据结构是一种存储和组织数据的方式,旨在便于访问和修改。又叫做折半查找,是一种非常高效的服务于有序数组的查找算法。在此,我将用二分查找作为查找算法的入门。

 目录

一、初识算法

1.1算法的定义:

1.2算法的特性1.2.1输入输出

1.2.2有穷性

1.2.3确定性

1.2.4可行性

1.3算法效率的度量方法

1.3.1事后统计方法

1.3.2事前分析估算方法

二、什么是数据结构?

2.1数据结构的基本概念和术语

2.1.1数据:

2.1.2数据元素

2.1.3数据项

2.1.4数据对象

2.1.5数据结构

2.2逻辑结构与物理结构

2.2.1逻辑结构

2.2.2物理结构(存储结构)

2.3抽象数据类型

2.3.1数据类型

2.3.2抽象数据类型

2.3.3图解总结

三、二分查找

3.1二分查找基础版

3.1.1二分查找介绍

3.1.2画图解释二分查找的原理

3.1.3二分查找的代码实现

注意事项:抛出问题?(上述代码left表示i,right表示j)

3.1.4二分查找代码优化

3.2二分查找改动版

3.2.1图解实现

3.2.2改动版注意事项

四、初识集合框架

4.1什么是集合框架

4.1.1类和接口总览

4.2集合框架的重要性

4.2.1开发中的使用

4.2.2笔试及面试题

4.3容器背后对应的数据结构

五、时间复杂度

5.1如何衡量一个算法的好坏

5.2算法效率

5.3时间复杂度

5.3.1时间复杂度的概念

5.3.2大O的渐进表示法

5.4推导大O阶方法

5.5常见时间复杂度计算举例

5.5.1第一题

5.5.2第二题

5.5.3第三题

5.5.4第四题

5.5.5第五题

5.5.6第六题

5.5.7第七题

六、空间复杂度

6.1空间复杂度计算举例

6.1.1第一题

6.1.2第二题

6.1.3第三题

6.1.4第四题


一、初识算法

定义:在数学和计算机科学领域,算法是一系列有限的严谨指令,通常用于解决一类特定问题或执行计算。不正式的说,算法就是任何定义优良的计算过程:接收一些值作为输入,在有限的时间内,产生一些值作为输出。

我们这节叫数据'结构,但很多时候我们会讲到算法,以及它们之间的关系。那到底是只讲数据结构呢,还是和算法一起讲?

只谈数据结构 , 当然是可以,我们可以在很短的时间就把几种重要的数据结构介绍完。听完后,很可能你没什么感觉,不知道这些数据结构有何用处 。 但如果我们再把相应的算法也拿来讲一讲,你就会发现似很难解决或者设法解决的问题,变得如此美妙和神奇。

1.1算法的定义:

       算法定义中,提到了指令,指令能被人或机器等计算装置执行。它可以是计算机指令,也可以是我们平时的语言文字。为了解决某个或某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定的功能,这就是算法了 。

1.2算法的特性

1.2.1输入输出

算法具有零个或多个输入。算法至少有一个或多个输出, 算法是一定需要输出的。(不需要输出,你用这个算法干吗?输出的形式可以是打印输出,也可以是返回一个或多个值等.)

1.2.2有穷性

有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每

一个步骤在可接受的时间内完成。

1.2.3确定性

确定性:算法的每一步骤都具有确定的含义 , 不会出现二义性 。

1.2.4可行性

可行性:算法的每一步都必须是可行的 , 也就是说,每一步都能够通过执行有限次数完成。

1.3算法效率的度量方法

1.3.1事后统计方法

事后统计方法:这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。

但这种方法显然是有很大缺陷的:

    • 必须依据算法事先编制好程序,这通常需要花费大量的时间和精力 。
    • 时间的比较依赖计算机硬件和软件等环境因素
    • 算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大关系

    1.3.2事前分析估算方法

           事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。

    如何进行事前分析估算,后面章节会具体介绍。

    二、什么是数据结构?

    定义:在计算机科学领域,数据结构是一种数据组织、管理和存储格式,通常被选择用来高效访问数据,数据结构是一种存储和组织数据的方式,旨在便于访问和修改

    2.1数据结构的基本概念和术语

    说到数据结构是什么,我们得先来谈谈什么叫数据?

    2.1.1数据:

           是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。

    数据不仅仅包括整型、实型等数值类型,还包括字符及声音、图像、视频等非数值类型 。比如我们现在常用的搜索引擎,一般会有网页、 M陀、图片、视频等分类。 MP3就是声音数据,图片当然是图像数据,视频就不用说了,而网页其实指的就是全部数据的搜索,包括最重要的数字和字符等文字数据 。

           这里的数据必须具备两个前提:

      • 可以输入到计算机中。
      • 能被计算机程序处理。
        对于整型、实型等数值类型,可以进行数值计算。
        对于字符数据类型,就需要进行非数值的处理。
        声音、图像、视频等其实是可以通过编码的手段变成字符数据来处理的 。

      2.1.2数据元素

             数据元素:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。 也被称为记录 。

      比如,在人类中,什么是数据元素呀? 当然是人了 。富类呢?哈 , 牛、马、羊、鸡、猪、 狗等动物当然就是禽类的数据元素。

      2.1.3数据项

             数据项:一个数据元素可以自若干个数据项组成 。

      比如人这样的数据元素,可以有眼、耳、鼻、嘴、 手、脚这些数据项,也可以有姓名、年龄、性别、出生地址、联系电话等数据项,具体有哪些数据项,要视你做的系统来决定。

      注意:数据项是数据不可分割的最小单位。

      但真正讨论问题时,数据元素才是数据结构中建立数据模型的着眼点

      2.1.4数据对象

             数据对象:是性质相同的数据元素的集合,是数据的子集。

      比如,还是刚才的例子,人都有姓名、生日、性别等相同的数据项。那么数据结构中的结构又是什么呢?

      2.1.5数据结构

             不同数据元素之间不是独立的,而是存在特定的关系,我们将这些关系称为结构。

             数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。

      2.2逻辑结构与物理结构

      按照视点的不同 , 我们把数据结构分为逻辑结构和物理结构。

      2.2.1逻辑结构

             逻辑结构:是指数据对象中数据元素之间的相互关系。

      1.集合结构

             集合结构:集合结构中的数据元素除了 同属于一个集合外,宫们之间没有其他关系。

      image.gif编辑

      2.线性结构

             线性结构:统性结构中的数据元素之间是一对一的关系

      image.gif编辑

      3.树形结构

             树形结构:树形结构中的数据元素之间存在一种一对多的层次关系

      image.gif编辑

      4.图形结构

             图形结构:图形结构的数据元素是多对多的关系

      注意:

        • 将每一个数据元素看做一个结点 ,用圈圈表示。
        • 元素之闯的逻辑关系用结点之间的连线表示.如果这个关系是有方向的,那么用带箭头的连续表示。

        2.2.2物理结构(存储结构)

               物理结构:是指数据的逻辑结构在计算机中的存储形式。

        根据物理结构的定义,实际上就是如何把数据元素存储到计算机的存储器中。存储器主要是针对内存而言的,像硬盘、软盘、光盘等外部存俯器的数据组织通常用文件结构来描述。

        1.顺序存储结构

               顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的

        image.gif编辑

        数组就是这样的顺序存储结构。当你告诉计算机,你要建立一个有 9 个整型数据的数组时,计算机就在内存中找了片空地, 按照一个整型所占位置的大小乘以 9 ,开辟一段连续的空间,于是第一个数组数据就放在第-个位置,第二个数据放在第二个,这样依次摆放。

        2.链式存储结构

                链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的 。

        现在如银行、医院等地方,设置了排队系统,也就是每个人丢了,先领一个号,等着叫号,叫到时去办理业务或者病。在等待的时候,你爱在哪在哪,可以坐着、站着或者走动,甚至出去逛一圈,只要及时回来就行 。 你关注的是前一个号有没有被叫到,叫到了,下一个就轮到了。

        2.3抽象数据类型

        2.3.1数据类型

              数据类型:是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。

        在高级语言中,每个变盘、常量和表达式都有各自的取值范围。类型就用来说明变量或表达式的取值范围和所能进行的操作 。

          • 结构类型:自若干个类型组合而戚,是可以再分解的。例如,整型数组是由若干整型数据组成的.
          • 原子类型:是不可再分解的,比如整型、浮点型、字符型。

                 抽象是指抽取出事物具有的普遍性的本质。

           事实上,高级语言的编程者不管最终程序运行在什么计算机上,他的目的就是为了实现两

          个整型数字的运算,如 a+b、 a-b、 axb 和 a/b 等,他才不关心整数在计算机内部是如何表示的 , 也不想知道 CPU 为了实现 1+2 进行几次开关操作,这些操作是如何实现的,对高级语言开发者来讲根本不重要。于是我们就会考虑,无论什么计算机、什么计算机语言,大都会面临着如整数运算、实数运算、字符运算等操作,我们可以考虑把它们都抽象出来。

          2.3.2抽象数据类型

                 抽象数据类型 (Abstract Dataη肘 , ADT) : 是指一个数学模型及定义在该模型上的一组操作。

          抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关。比如刚才的例子, 各个计算机,不管是大型机、小型机、 町、平板电脑、 PDA .

          甚至智能手机都拥有 "整数"类型,也需要整数阔的运算,那么整型其实就是一个抽象数据类型,尽管它在上面提到的这些在不同计算机中实现方法上可能不一样,但由于其定义的数学特性相向,在计算机掬程者看来,它们都是捆同的。因此, "抽象'的意义在于数据类型的数学抽象特性 。等价于Java中的抽象类的思想。抽象数据类型体现了程序设计中问题分解、抽象和信息隐藏的特性。

           

          2.3.3图解总结

          image.gif编辑

          image.gif编辑

          三、二分查找

          接下来,我将通过著名的二分查找算法引入我们对算法的认识。

          3.1二分查找基础版

          3.1.1二分查找介绍

          二分查找:又叫做折半查找,是一种非常高效的服务于有序数组的查找算法。在此,我将用二分查找作为查找算法的入门。前提是线性表中的记录必须是有序(通常从小到大有序)

          image.gif编辑

          首先,我们看二分查找的需求:

                 在有序数组 Array 内,查找目标值:target

          思路分析:

          折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查
          找 ;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

            • 如果找到查找值target,则返回查找值所在的索引。
            • 如果没有找到,则返回-1。

            3.1.2画图解释二分查找的原理

            二分查找的规则:

            1.数组元素:有序排列,可以出现相同元素
            2.假设我们
            需要查找的值target为:xx
            3.
            数组名  .length  :访问数组的大小

            4.设置指针 left=0,right=arr.length-1(如果为arr.length访问数组元素会出现越界 ),查找范围在0~arr.length-1的范围

            5.当  i>j  表明在数组i~j下标元素范围内没找到,此时退出循环

            6.设置 指针mid=(left+right)/2;(表示我们得到中间元素的下标)
            7.
            判断:如果arr[mid]<target,说明数组的中间元素小于查找值,即中间元素的左侧不再参与运算,我们让i=mid+1,缩小范围到右边范围,跳到第6步。如果arr[mid]>target,表明arr[mid]的右侧没有查找值,缩小mid的范围到左边范围我们让j=mid-1,跳到第6步。

            8.如果arr[mid]==target表明当前位置为我们要找的值,返回下标mid。

            9.如果循环结束后没找到查找值,返回-1

            假设:

                   arr={5,8,11,17,26,30,37,40};

                   arr.length=8;

                   i=0, j=arr.length-1=8-1=7;

            查找 30 这个元素是否在数组中---->

            如果我们查找值在数组元素中:

            image.gif编辑

            image.gif编辑

            接下来我们进行判断:

            arr[3] = 17;我们发现17<30,让 i = mid + 1 = 4 ;跳到第六步。

            image.gif编辑

            image.gif编辑

            此时我们再次进行判断: arr [5]==30,此时等于target值,返回mid=5。

            通过在有序数组中查找30的位置,我们进行了两次循环就得到了查找值的索引。

            如果查找值不在数组范围内:

            假设:

                   arr={5,8,11,17,26,30,37,40};

                   arr.length=8;

                   i=0,j=arr.length-1=8-1=7;

            查找 15 这个元素是否在数组中---->

            image.gif编辑

            接下来我们进行判断:

            得出:arr[3]==17>15,j = mid -1 = 3 - 1 =2 ;

            image.gif编辑

            image.gif编辑

            此时再次进行判断:

            我们发现arr[mid]==8<15, i = mid + 1 = 1 + 1 = 2;跳到第6步。

            image.gif编辑

            image.gif编辑

            此时我们再次进行判断:

            得出arr[mid]= 11 < 15,i = mid + 1 = 2 + 1 = 3;退出循环

            image.gif编辑

            退出循环后没有找到查找值15,返回-1。

            通过上面的图解,我们通过Java代码实现二分查找:

            (此时 left 表示上图的 i , right 表示上图的 j )

            3.1.3二分查找的代码实现

            public static int binarySearch(int k,int[] arr){
                                               //设置指针和初值
                    int left=0;                //最左端下标记录首位
                    int right=arr.length-1;    //最右端下标记录尾位
                    while(left<=right){
                        count++;
                        int mid=(left+right)/2;//折半
                        if(arr[mid]<k){        //如果目标值大于中值,则最左端下标调整到中值下标的下一位
                            left=mid+1;
                        }
                        else if(arr[mid]>k){   //如果目标值小于中值,则最右端下标调整至中值下标的前一位
                            right=mid-1;
                        }
                        else if(arr[mid]==k){   若相等,mid即为目标值所在下标
                             return mid;
                        }
                    }
                    return -1;
                }

            image.gif

            梳理二分查找的代码实现逻辑:

            1.首先设置两个指针指向数组的两端。

            2.当left在right的左侧或者两者相较的情况下则进入循环

            3.设置mid 记录数组的中间值,如果目标值大于中间值,说明目标值在中间值的右侧,我们通过修改 left 的值让mid的值偏向右边;如果目标值小于中间值,说明目标值在中间值的左侧,我们通过修改 right 的值让mid的值偏向左边;反复进行循环,我们发现如果 left 的值>(大于) right的值,循环会反复进行达不到我们的需求,最终将left 控制在 left<=right ,如果不满足这个条件证明没有找到查找值,返回-1。

            4.在循环内部,我们通过中间值arr[mid]与目标值进行比较,最终经过反复修改mid的值达到对目标值的不断靠近,最终找到目标值的下标。返回目标值的下标

            注意事项:抛出问题?(上述代码left表示i,right表示j)

            1.为什么循环的判断条件要求i<=j,如果i<j会发生什么情况呢?

            2.为什么j的值为arr.length-1,如果不减1可以吗?

            3.为什么 mid=(left+right)/2,如果是7 / 2=3.5这样有小数不就不对了吗?

             

            问题一:如果i < j ,那么我们在进行二分时查找下标为0的目标,可能存在刚好i 和 j 的位置在同一个位置,此时刚好mid==查找值索引。本来可以找到,但是我们循环条件判断失败i和j不会进行比较导致退出循环返回-1,结果错误。

            问题二:arr.length-1表示数组最后一个元素的下标,在上述二分查找的代码实现中,j是有数组的具体元素的,会被我们包括在内。如果变成arr.length,导致不在数组最大元素的下标,此时arr[j]访问不到。后面有改进版本解决这个问题。

            问题三:mid 是 int型数组,(left+right)/2如果没有浮点型数组或者小数点的标志,会发生隐式类型转换(博主后续博客更新)。

            3.1.4二分查找代码优化

            mid=(left+right)/2,这句代码有问题吗?此时我们思考一下?

            int left=0;
            int right=2147483647;
            int mid=(left+right)/2=1073741823;
            //假如进行第二次循环
            left=mid+1;
            mid=(left+right)/2;

            image.gif

            image.gif编辑

            通过计算,我们得到int型数据的最大值。在二分查找中如果我们的数组大小有2147483647个数据,那么right=2147483647-1=2147483646,mid=(2141473646+0)/2=1,073,741,823,进行第一次循环后,可能出现left=mid+1=1,073,741,824,第二次循环时mid值=(1,073,741,824+2147483646)/2=3,221,225,470

            image.gif编辑

            由于3,221,225,470超出了int的表示范围,当我们int型数据进行运算时,image.gif编辑

            此时最高位不是符号位,所以可以得到结果。但是在Java中最高位认为是符号位,最高位表示负数,最高位是1,即-1073741826。此时,我们需要对代码进行优化。此处相关内存的原码反码补码知识查看博主的博客。

            public static int binarySearch(int k,int[] arr){
                    int left=0;
                    int right=arr.length-1;
                    while(left<=right){
                        int mid=(left+right)>>>1;
                        if(arr[mid]<k){
                            left=mid+1;
                        }
                        else if(arr[mid]>k){
                            right=mid-1;
                        }
                        else if(arr[mid]==k){
                             return mid;
                        }
                    }
                    return -1;
                }

            image.gif

            无符号右移:>>>表示右移后左边始终补0

            右移:>>表示右移后左边补符号位(0为正,1为负)

            8的二进制位:1000

            8>>>1  :  8无符号右移1个二进制位变为:0100 ==4

            也就是说一个数右移相当于除以2,同时无符号右移可以避免出现负数的情况。

            我们将(left+right)/2修改成(left+right)>>>1

            3.2二分查找改动版

            public static int binarySearch(int target,int[] arr){
                    int left=0;
                    int right=arr.length;
                    while(left<right){
                        int mid=(left+right)>>>1;
                        if(arr[mid]<target){
                            left=mid;
                        }
                        else if(arr[mid]>target){
                            right=mid;
                        }
                        else if(arr[mid]==target){
                            return mid;
                        }
                    }
                    return -1;
                }

            image.gif

            3.2.1图解实现

            Array={4,7,10,16,23,29,31,38}当我们查找的值为7的时候:

            image.gif编辑

            image.gif编辑

            此时进行判断:发现中间值小于目标值,right=mid(因为right表示的是一个边界,指向的一定不是查找目标,恰巧和指向的查找目标错过。)

            image.gif编辑

            因为在第一轮中23已经和1(目标值)比较过了,所以right指向的23并不在查找目标范围内

            image.gif编辑

            此时中间值还是大于目标值,right=mid;

            image.gif编辑

            image.gif编辑

            此时找到目标值7,返回mid==1

            3.2.2改动版注意事项

            如果循环的条件变为:left<=right会在某些情况发生死循环

            因为进行某轮循环后left和right的值相等,此时mid也可能等于left等于right,right可能等于mid返回进行陷入死循环。

            四、初识集合框架

            4.1什么是集合框架

            Java 集合框架 Java Collection Framework ,又被称为容器 container ,是定义在 java.util 包下的一组接口 interfaces和其实现类 classes。其主要表现为将多个元素 element 置于一个单元中,用于对这些元素进行快速、便捷的存储 store 、检索 retrieve 、管理 manipulate ,即平时我们俗称的增删查改 CRUD。

            例如,一副扑克牌(一组牌的集合)、一个邮箱(一组邮件的集合)、一个通讯录(一组姓名和电话的映射关系)等等

            4.1.1类和接口总览

            image.gif编辑

            4.2集合框架的重要性

            4.2.1开发中的使用

              • 使用成熟的集合框架,有助于我们便捷、快速的写出高效、稳定的代码
              • 学习背后的数据结构知识,有助于我们理解各个集合的优缺点及使用场景

              4.2.2笔试及面试题

              腾讯-Java后台开发面经

              1. HashMap 了解不,介绍一下,如果一个对象为 key 时,hashCode 和 equals 方法的用法要注意什么?

              2. HashSet 和 HashMap 的区别是什么?

              3. HashMap 是线程安全的么?那需要线程安全需要用到什么?

              阿里巴巴-Java后台开发面经

              1. ArrayList 和 LinkedList 的区别是什么?

              2. 有了解过 HashMap 的具体实现么?

              3. HashMap 和 ConcurrentHashMap 哪个效率更高?

              今日头条-Java后台开发面经

              1. 编程题:判断一个链表是否是一个回文链表。

              2. Redis 的 zset 类型对应到 java 语言中大致是什么类型?

              3. hashCode 主要是用来做什么用的?

              4.3容器背后对应的数据结构

              1. Collection:是一个接口,包含了大部分容器常用的一些方法

              2. List:是一个接口,规范了ArrayList 和 LinkedList中要实现的方法

              ArrayList:实现了List接口,底层为动态类型顺序表

              LinkedList:实现了List接口,底层为双向链表

              3. Stack:底层是栈,栈是一种特殊的顺序表

              4. Queue:底层是队列,队列是一种特殊的顺序表

              5. Deque:是一个接口

              6. Set:集合,是一个接口,里面放置的是K模型

              HashSet:底层为哈希桶,查询的时间复杂度为O(1)

              TreeSet:底层为红黑树,查询的时间复杂度为O( ),关于key有序的

              7. Map:映射,里面存储的是K-V模型的键值对

              HashMap:底层为哈希桶,查询时间复杂度为O(1)

              TreeMap:底层为红黑树,查询的时间复杂度为O( ),关于key有序

              五、时间复杂度

              5.1如何衡量一个算法的好坏

              下面求斐波那契数列的算法好还是不好,为什么?该如何衡量一个算法的好坏呢?

              public static long Fib(int N){
                  if(N < 3){
                      return 1;
                  } 
                  return Fib(N-1) + Fib(N-2);
              }

              image.gif

              5.2算法效率

              算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称作空间复杂度。时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间,在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度

              5.3时间复杂度

              5.3.1时间复杂度的概念

              时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度

              5.3.2大O的渐进表示法

              // 请计算一下func1基本操作执行了多少次?
                  void func1(int N){
                      int count = 0;
                      for (int i = 0; i < N ; i++) {
                          for (int j = 0; j < N ; j++) {
                              count++;
                          }
                      }
                      for (int k = 0; k < 2 * N ; k++) {
                          count++;
                      } int M = 10;
                      while ((M--) > 0) {
                          count++;
                      } System.out.println(count);
                  }

              image.gif

              基本操作次数:F(N)=N*N+2*N+10

              当:

                     N = 10        F(N) = 130

                     N = 100      F(N) = 10210

                     N = 1000    F(N) = 1002010

              实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。大O符号(Big O notation):是用于描述函数渐进行为的数学符号

              5.4推导大O阶方法

              1.用常数1取代运行时间中的所有加法常数。
              2、在修改后的运行次数函数中,只保留最高阶项。
              3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶

              使用大O的渐进表示法以后,Func1的时间复杂度为:O(N^2)

              通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数

              4.算法时间度的好坏:

              最坏情况:任意输入规模的最大运行次数(上界)

              平均情况:任意输入规模的期望运行次数

              最好情况:任意输入规模的最小运行次数(下界)

              例如:在一个长度为N数组中搜索一个数据x

              最好情况:1次找到

              最坏情况:N次找到

              平均情况:N/2次找到

              实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

              5.5常见时间复杂度计算举例

              5.5.1第一题

              // 计算func2的时间复杂度?
                  void func2(int N) {
                      int count = 0;
                      for (int k = 0; k < 2 * N ; k++) {
                          count++;
                      } int M = 10;
                      while ((M--) > 0) {
                          count++;
                      } System.out.println(count);
                  }

              image.gif

              F(N)=2*N+10,时间复杂度:O(N)

              5.5.2第二题

              // 计算func3的时间复杂度?
                  void func3(int N, int M) {
                      int count = 0;
                      for (int k = 0; k < M; k++) {
                          count++;
                      } for (int k = 0; k < N ; k++) {
                          count++;
                      } System.out.println(count);
                  }

              image.gif

              O(M+N):因为M和N具体未知

              5.5.3第三题

              // 计算func4的时间复杂度?
                  void func4(int N) {
                      int count = 0;
                      for (int k = 0; k < 100; k++) {
                          count++;
                      } System.out.println(count);
                  }

              image.gif

              O(1):N和执行100次没有关系

              5.5.4第四题

              // 计算bubbleSort的时间复杂度?
                  void bubbleSort(int[] array) {
                      for (int end = array.length; end > 0; end--) {
                          boolean sorted = true;
                          for (int i = 1; i < end; i++) {
                              if (array[i - 1] > array[i]) {
                                  Swap(array, i - 1, i);
                                  sorted = false;
                              }
                          } if
                          (sorted == true) {
                              break;
                          }
                      }
                  }

              image.gif

              没有优化的冒牌排序:

              思路:第一轮:1~(N-1);第二轮:1~(n-2);第三轮:1~(N-3);第N-1轮:1

              计算:(N-1)+(N-2)+(N-3)+...+1:等差数列求和公式:Sn=(1+N-1)*(N-1)/2=n*(n-1)/2

              时间复杂度:O(N^2)

              优化后的冒泡排序:

              可以达到O(N):如果后面有序进行一轮排序已经按顺序。

              5.5.5第五题

              // 计算binarySearch的时间复杂度?
                  int binarySearch(int[] array, int value) {
                      int begin = 0;
                      int end = array.length - 1;
                      while (begin <= end) {
                          int mid = begin + ((end-begin) / 2);
                          if (array[mid] < value)
                              begin = mid + 1;
                          else if (array[mid] > value)
                              end = mid - 1;
                          else
                              return mid;
                      } return -1;
                  }

              image.gif

              每次查找数据如果没有找到则折半处理:

              思路:第一次折半:N/2;第二次折半:N/2/2==N//(2^2);第三次折半:N/2/2/2==N/(2^3)

              计算:N/(2^x)==1--->2^x==N--->x=logN(以2为底)

              时间复杂度:O(logN)

              5.5.6第六题

              // 计算阶乘递归factorial的时间复杂度?
                  long factorial(int N) {
                      return N < 2 ? N : factorial(N-1) * N;
                  }

              image.gif

              在递归求解阶乘的过程中,每一次调用递归函数都会降低 n 的值,并将计算的任务交给下一层递归函数。因此,递归求解阶乘的时间复杂度与递归的深度有关。在最坏情况下,递归深度为 n,这时需要执行 n 个递归函数。每个函数都需要进行一次乘法运算,因此总的时间复杂度为 O(n)。

              注意:递归的时间复杂度=递归的次数*每次递归执行的次数

              5.5.7第七题

              // 计算斐波那契递归fibonacci的时间复杂度?
                  int fibonacci(int N) {
                      return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
                  }

              image.gif

              画图分析:

              image.gif编辑image.gif编辑

              多路递归思路:第一次:递归一路;第二次:递归两路;第三次:递归四路;第四次:递归8路......不考虑F(1)的空缺情况下进行推到

              计算:1+2+4+...+2^(N-1)---->符合等比数列

              (1*(1-2^N))/(1-2)(不考虑负号)的时间复杂度为---->(1*(1-2^N))/(1-2):O(2^N);

              六、空间复杂度

              空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法

              6.1空间复杂度计算举例

              6.1.1第一题

              // 计算斐波那契递归fibonacci的时间复杂度?
                  int fibonacci(int N) {
                      return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
                  }
                  // 计算bubbleSort的空间复杂度?
                  void bubbleSort(int[] array) {
                      for (int end = array.length; end > 0; end--) {
                          boolean sorted = true;
                          for (int i = 1; i < end; i++) {
                              if (array[i - 1] > array[i]) {
                                  Swap(array, i - 1, i);
                                  sorted = false;
                              }
                          }
                          if
                          (sorted == true) {
                              break;
                          }
                      }
                  }

              image.gif

              变量有一个sorted、end:空间复杂度:O(1)

              6.1.2第二题

              // 计算fibonacci的空间复杂度?
                  long[] fibonacci(int n) {
                      long[] fibArray = new long[n + 1];
                      fibArray[0] = 0;
                      fibArray[1] = 1;
                      for (int i = 2; i <= n ; i++) {
                          fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
                      }
                      return fibArray;
                  }

              image.gif

              开辟了一个N+1大小的数组,空间复杂度:O(N)

              6.1.3第三题

              // 计算阶乘递归Factorial的空间复杂度?
              long factorial(int N) {
                  return N < 2 ? N : factorial(N-1)*N;
              }

              image.gif

              空间复杂度:O(N)

              6.1.4第四题

              //计算斐波那契数列的空间复杂度
              int fibonacci(int N) {
                      return N < 2 ? N : fibonacci(N - 1) + fibonacci(N - 2);
                  }

              image.gif

              image.gif编辑

              空间复杂度为:O(N)

              目录
              相关文章
              |
              1月前
              |
              存储 人工智能 算法
              数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
              这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
              70 3
              数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
              |
              28天前
              |
              存储 Java
              Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
              【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
              42 1
              |
              30天前
              |
              存储 Java
              告别混乱!用Java Map优雅管理你的数据结构
              【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
              82 2
              |
              30天前
              |
              存储 Java 开发者
              Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
              【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
              61 2
              |
              13天前
              |
              缓存 算法 Java
              本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
              在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
              35 6
              |
              18天前
              |
              存储 Java 索引
              Java中的数据结构:ArrayList和LinkedList的比较
              【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
              |
              26天前
              |
              存储 算法 Java
              Java 中常用的数据结构
              【10月更文挑战第20天】这些数据结构在 Java 编程中都有着广泛的应用,掌握它们的特点和用法对于提高编程能力和解决实际问题非常重要。
              25 6
              |
              28天前
              |
              存储 Java 开发者
              Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
              【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
              27 1
              |
              1月前
              |
              存储 算法 Java
              Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
              Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
              33 4
              |
              1月前
              |
              存储 算法 Java
              Java常用的数据结构
              【10月更文挑战第3天】 在 Java 中,常用的数据结构包括数组、链表、栈、队列、树、图、哈希表和集合。每种数据结构都有其特点和适用场景,如数组适用于快速访问,链表适合频繁插入和删除,栈用于实现后进先出,队列用于先进先出,树和图用于复杂关系的表示和查找,哈希表提供高效的查找性能,集合用于存储不重复的元素。合理选择和组合使用这些数据结构,可以显著提升程序的性能和效率。
              下一篇
              无影云桌面