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月前
              |
              存储 算法 安全
              探究‘公司禁用 U 盘’背后的哈希表算法与 Java 实现
              在数字化办公时代,信息安全至关重要。许多公司采取“禁用U盘”策略,利用哈希表算法高效管理外接设备的接入权限。哈希表通过哈希函数将设备标识映射到数组索引,快速判断U盘是否授权。例如,公司预先将允许的U盘标识存入哈希表,新设备接入时迅速验证,未授权则禁止传输并报警。这有效防止恶意软件和数据泄露,保障企业信息安全。 代码示例展示了如何用Java实现简单的哈希表,模拟公司U盘管控场景。哈希表不仅用于设备管理,还在文件索引、用户权限等多方面助力信息安全防线的构建,为企业数字化进程保驾护航。
              |
              2月前
              |
              监控 算法 网络协议
              Java 实现局域网电脑屏幕监控算法揭秘
              在数字化办公环境中,局域网电脑屏幕监控至关重要。本文介绍用Java实现这一功能的算法,涵盖图像采集、数据传输和监控端显示三个关键环节。通过Java的AWT/Swing库和Robot类抓取屏幕图像,使用Socket进行TCP/IP通信传输图像数据,并利用ImageIO类在监控端展示图像。整个过程确保高效、实时和准确,为提升数字化管理提供了技术基础。
              84 15
              |
              16天前
              |
              存储 机器学习/深度学习 算法
              C 408—《数据结构》算法题基础篇—链表(下)
              408考研——《数据结构》算法题基础篇之链表(下)。
              79 29
              |
              16天前
              |
              存储 算法 C语言
              C 408—《数据结构》算法题基础篇—链表(上)
              408考研——《数据结构》算法题基础篇之链表(上)。
              72 25
              |
              13天前
              |
              存储 算法 Java
              解锁“分享文件”高效密码:探秘 Java 二叉搜索树算法
              在信息爆炸的时代,文件分享至关重要。二叉搜索树(BST)以其高效的查找性能,为文件分享优化提供了新路径。本文聚焦Java环境下BST的应用,介绍其基础结构、实现示例及进阶优化。BST通过有序节点快速定位文件,结合自平衡树、多线程和权限管理,大幅提升文件分享效率与安全性。代码示例展示了文件插入与查找的基本操作,适用于大规模并发场景,确保分享过程流畅高效。掌握BST算法,助力文件分享创新发展。
              |
              16天前
              |
              存储 人工智能 算法
              C 408—《数据结构》算法题基础篇—数组(通俗易懂)
              408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
              58 23
              |
              26天前
              |
              存储 人工智能 算法
              解锁分布式文件分享的 Java 一致性哈希算法密码
              在数字化时代,文件分享成为信息传播与协同办公的关键环节。本文深入探讨基于Java的一致性哈希算法,该算法通过引入虚拟节点和环形哈希空间,解决了传统哈希算法在分布式存储中的“哈希雪崩”问题,确保文件分配稳定高效。文章还展示了Java实现代码,并展望了其在未来文件分享技术中的应用前景,如结合AI优化节点布局和区块链增强数据安全。
              |
              27天前
              |
              算法 安全 Java
              Java线程调度揭秘:从算法到策略,让你面试稳赢!
              在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
              68 16
              |
              1月前
              |
              运维 监控 算法
              企业局域网监控软件中 Java 优先队列算法的核心优势
              企业局域网监控软件是数字化时代企业网络安全与高效运营的基石,犹如一位洞察秋毫的卫士。通过Java实现的优先队列算法,它能依据事件优先级排序,确保关键网络事件如异常流量、数据泄露等被优先处理,保障系统稳定与安全。代码示例展示了如何定义网络事件类并使用PriorityQueue处理高优先级事件,尤其在面对疑似风险时迅速启动应急措施。这一核心技术助力企业在复杂网络环境中稳健前行,护航业务腾飞。
              65 32
              |
              1月前
              |
              存储 监控 算法
              剖析基于Java算法驱动的智能局域网管控之道
              本文探讨了基于Java语言的局域网控制方案,结合链表数据结构与令牌桶算法,解决设备管理和流量调度难题。通过链表灵活存储网络设备信息,实现高效设备管理;令牌桶算法则精准控制流量,确保网络平稳运行。二者相辅相成,为校园、企业等局域网提供稳固高效的控制体系,保障业务连续性和数据安全。

              热门文章

              最新文章