手撕数据结构与算法-开篇

1. 浪子回头

2019年,这个不平凡的一年,中美贸易战、各个大厂裁人。形成了如今互联网行情很差,形势很严峻啊。有的人说今年是互联网过去十年中最差的一年,也多是将来十年中最好的一年。身处这样乱世的咱们怎么办?我也听不少朋友说,今年的面试都比较严格,特色是"要求高、薪资低"。也常常听见他们说某某大厂考了个手写算法,结果当场挂了。身为程序员的咱们,再这样行业形势严峻、竞争压力大的状况下,只有不断提高自身能力,以确保在行业内能有个立锥之地java

《数据结构与算法》在我学生时代就是一门让我望而止步的课程。听着名字就感受很晦涩难懂、须要大量的数学知识作铺垫。相信不少人也都和我同样,上学的时候学的只知其一;不知其二,到了工做之后也不多用到就不了了之了。可是它却成为了你面试、寻找好的平台的障碍。不少大厂都很看中程序员的基本功,因此在面试中算法就编程了常考题目,为何呢?由于基础知识就像是一座大楼的地基,它可以决定你技术的高度深度。因此通常大厂都是看中你有没有这个技术发展的潜力。("因此你们要夯实基本功了。")程序员

在我看来后端程序员应该学的有三大基础知识"数据结构与算法""计算机系统""操做系统Linux"。在这我的人都必需要手撕算法的时代,彻夜难眠的我(纯属扯淡)决定带领你们一块儿学习三大基础知识,本次开篇系列是《手撕数据结构与算法》,每个系列更完就会开启下一个系列,你们不要着急。面试

注意,注意前方高能======>(广告植入)算法

若是你对个人这个系列感兴趣能够关注个人公众号,带你走上”超神之路、拿高薪offer、当上技术专家、出任个大厂、迎娶白富美、走上人生巅峰,想一想还有点小激动。” (请容许我吹个🐂)编程

来了 来了 他来了 他带着二维码来了!!!后端

欢迎扫码关注哦!!!

2. 数学知识复习

在咱们系统的学习数据结构与算法以前,咱们先简单的复习几个数学知识,相信你们也都忘的差很少了,是否是都学完了又还给老师了呢?嫑急,跟我一块儿来复习一下。数组

2.1. 指数

指数是幂运算aⁿ(a≠0)中的一个参数,a为底数,n为指数,指数位于底数的右上角,幂运算表示指数个底数相乘。当n是一个正整数,a表示n个a连乘。当n=0时,aⁿ=1。《百度百科》数据结构

  • 指数:就是aⁿ中的n。
  • 底数:就是aⁿ的a
  • 幂运算:指数个底数相乘。

幂运算公式: 函数

2.2. 对数

$ a^{x}=n$ 若是a的x次方等于N(a>0,且a不等于1),那么数x叫作以a为底N的对数(logarithm),记做$x=log_{a}N$。其中,a叫作对数的底数,N叫作真数。《百度百科》性能

在计算机科学中,除非有特别的声明,不然全部的对数都是以2为底的。

公式:

简单列了两个公式,你们看看就好了,知道一下啥是对数

3. 时间复杂度

对于算法时间复杂度,可能有的朋友可能想了,不就是估算一段代码的执行时间嘛,咱们能够搞个监控啊,看看一下每一个接口的耗时不就行了,何须那么麻烦,还要分析下时间复杂度。可是这个监控属于过后操做,只有代码在运行时,才能知道你写的代码效率高不高,那么如何在写代码的时候就评估一段代码的执行效率呢,这个时候就须要时间复杂度来分析了。你们日常写代码能够结合时间复杂度和监控作好事前过后的分析,更好的优化代码。

3.1 大O表示法

由于渐进时间复杂度使用大写O来表示,因此也称大O表示法。例如: O(f(n))

常见时间复杂度:

常见时间复杂度所耗费时间从小到大依次是:

推导大O的方法:

3.2 如何分析时间复杂度

  • O(1)

    int i = 5;         /*执行一次*/
    int j = 6;         /*执行一次*/
    int sum = j + i;   /*执行一次*/

    这段代码的运行函数应该是f(n)=3 ,用来大O来表示的话应该是O(f(n))=O(3) ,可是根据咱们的推导大O表示法中的第一条,要用1代替函数中的常数,因此O(3)=>O(1),那么这段代码的时间复杂度就是O(1)而不是O(3)。

  • O(logn)

    int count = 1;             /*执行一次*/
    int n = 100;               /*执行一次*/
    while (count < n) {
        count = count * 2;     /*执行屡次*/
    }

  • O(n)

    for (int k = 0; k < n; k++) {
        System.out.println(k);   /*执行n次*/
    }

    这段代码的执行次数会随着n的增大而增大,也就是说会执行n次,因此他的时间复杂度就是O(n)。

  • $O(n^{2})$

    for (int k = 0; k < n; k++) {
       for (int l = 0; l < n; l++) {
          System.out.println(l);      /*执行了n*n次/
       }
    }

读到这里不知道你们学会了没有?其实分析一段代码的时间复杂度,就找到你代码中执行次数最多的地方,分析一下它的时间复杂度是什么,那么你整段代码的时间复杂度就是什么。以最大为准。

3.3 时间复杂度量级

public int find(int[] arrays, int findValue) {
        int result = -1;                                /*执行一次*/
        int n = arrays.length;
        for (int i = 0; i < n; i++) {
            if (arrays[i] == findValue) {               /*执行arrays.length次*/
                result = arrays[i];
                break;
            }
        }
        return result;                                 /*执行一次*/
    }

咱们来分析一下上边这个方法,这个方法的做用是从一个数组中查找到它想要的值。其实一个算法的复杂度还会根据实际的执行状况有必定的变化,就好比上边这段代码,假如数组的长度是100,里面存的是1-100的数。

  • 最好状况时间复杂度

    若是我在这个数组里面查找数字1,那么在它第一次遍历的时候就找到了这个值,而后就执行break结束当前循环,此时全部的代码只执行了一次,属于常数阶O(1),这就是最好状况下这段代码的时间复杂度。

  • 最坏状况时间复杂度

    若是我在这个数组里面查找数字100,那么这个数组就要被遍历一边才能找到并返回,这样的话这个方法就要受到数组大小的影响了,若是数组的大小为n,那么就是n越大,执行次数越多。属于线性阶O(n) ,这就是最坏状况下的时间复杂度。

  • 平均状况时间复杂度

    咱们都知道最好、最坏时间复杂度都是在两种极端状况下的代码复杂度,发生的几率并不高,因次咱们引入另外一个概念“平均时间复杂度”。咱们还看上边的这个方法,要查找个一个数有n+1中状况:在数组0 ~ n-1的的位置中和再也不数组中,因此咱们将全部代码的执行次数累加起来((1+2+3+...+n)+n),而后再除以全部状况n(n+1),就获得须要执行次数的平均值了。

推导过程:

大O表示法,会省略系数、低阶、常量,因此平均状况时间复杂度是O(n)

可是这个平均复杂度没有考虑各自状况的发生几率,这里的n+1个状况,它们的发生几率是不同的,因此还须要引入各自状况发生的几率再具体分析。findValue要么在1~n中,要么不在1~n中,因此他们的几率都是$\frac{1}{2}$,同时数据在1~n中的各个位置的几率同样为$\frac{1}{n}$ ,根据几率乘法法则,findValue在1~n中的任意位置的几率是$\frac{1}{2n}$ ,所以在上边推导的基础上须要在加入几率的的发生状况。

考虑几率的平均状况复杂度为:

推导过程:

这就是几率论中的加权平均值,也叫作指望值,因此平均时间复杂度全称叫:加权平均时间复杂度或者指望时间复杂度。平均复杂度变为$O(\frac{3n+1}{4})$,忽略系数及常量后,最终获得加权平均时间复杂度为O(n)。

4. 空间复杂度

算法的空间复杂度是对运行过程当中临时占用存储空间大小的度量,算法空间复杂度的计算公式记做:S(n) = O(f(n)),n为问题规模,f(n)为语句关于n所占存储空间函数。因为空间复杂度和时间复杂度的大O表示法相同,因此咱们就简单介绍下。

常见的空间复杂度从低到高是:

4.1 如何分析空间复杂度

  • O(1)

    public static void intFun(int n) {
       var intValue = n;
       //...
    }

    当算发的存储空间大小固定,和输入的规模没有直接的关系时,空间复杂度就记做O(1),就像上边这个方法,无论你是输入10,仍是100,它占用的内存都是4字节。

  • O(n)

    public static void arrayFun(int n) {
       var array = new int[n];
       //...
    }

    当算法分配的空间是一个集合或者数组时,而且它的大小和输入规模n成正比时,此时空间复杂度记为$O(n)$。

  • $O(n^{2})$

    public static void matrixFun(int n) {
       var matrix = new int[n][n];
       //...
    }

    当算法分配的空间是一个二维数组,而且它的第一维度和第二维度的大小都和输入规模n成正比时,此时空间复杂度记为$O(n^{2})$ 。

5. 总结

对于时间空间的取舍,咱们就要根据具体的业务实际状况而定,有的时候就须要牺牲时间来换空间,有的时候就须要牺牲空间来换时间,在如今这个计算机硬件性能飙升的时代,固然咱们仍是喜欢选择牺牲空间来换时间,毕竟内存仍是有的,也不贵。而且能够提升效率给用户更好的体验。

  • 什么是时间复杂度?

    时间复杂度就是对算法运行时间长短的度量,用大O表示为T(n) = O(f(n)) 。常见的时间复杂度从低到高的顺序是:

  • 什么是空间复杂度?

    空间复杂度是对算法运行时所占用的临时存储空间的度量,用大O标识为S(n)= O(f(n)) 。常见的空间复杂度从低到高的顺序是:

6. 参考

  1. 《数据结构与算法分析》
  2. 《大话数据结构》
  3. 《漫画算法》

能看到这里的朋友,相信你也对学习保持着必定的热情,以为对你有帮助的话麻烦点个赞或在看以资鼓励吧,有什么问题欢迎留言或者关注我公众号进群交流。另外文章有理解错误、写错、说错的地方,但愿你们指正,这是对我最大的帮助,谢谢你们。

相关文章
相关标签/搜索