手撕數據結構與算法-開篇

1. 浪子回頭

2019年,這個不平凡的一年,中美貿易戰、各個大廠裁人。形成了如今互聯網行情很差,形勢很嚴峻啊。有的人說今年是互聯網過去十年中最差的一年,也多是將來十年中最好的一年。身處這樣亂世的咱們怎麼辦?我也聽不少朋友說,今年的面試都比較嚴格,特色是"要求高、薪資低"。也常常聽見他們說某某大廠考了個手寫算法,結果當場掛了。身爲程序員的咱們,再這樣行業形勢嚴峻、競爭壓力大的狀況下,只有不斷提高自身能力,以確保在行業內能有個立錐之地java

《數據結構與算法》在我學生時代就是一門讓我望而止步的課程。聽着名字就感受很晦澀難懂、須要大量的數學知識作鋪墊。相信不少人也都和我同樣,上學的時候學的只知其一;不知其二,到了工做之後也不多用到就不了了之了。可是它卻成爲了你面試、尋找好的平臺的障礙。不少大廠都很看中程序員的基本功,因此在面試中算法就編程了常考題目,爲何呢?由於基礎知識就像是一座大樓的地基,它可以決定你技術的高度深度。因此通常大廠都是看中你有沒有這個技術發展的潛力。("因此你們要夯實基本功了。")git

在我看來後端程序員應該學的有三大基礎知識"數據結構與算法""計算機系統""操做系統Linux"。在這我的人都必需要手撕算法的時代,徹夜難眠的我(純屬扯淡)決定帶領你們一塊兒學習三大基礎知識,本次開篇系列是《手撕數據結構與算法》,每個系列更完就會開啓下一個系列,你們不要着急。程序員

先放個圖,讓你們瞭解下本系列都要講的內容github

歡迎掃碼關注哦!!!

注意,注意前方高能======>(廣告植入) web

本篇文章已收錄到github,點擊這裏便可訪問 手撕數據結構與算法

若是你對個人這個系列感興趣能夠關注個人公衆號,帶你走上」超神之路、拿高薪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>0,且a不等於1),那麼數x叫作以a爲底N的對數(logarithm),記做。其中,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)。

  • 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中,因此他們的機率都是,同時數據在1~n中的各個位置的機率同樣爲 ,根據機率乘法法則,findValue在1~n中的任意位置的機率是 ,所以在上邊推導的基礎上須要在加入機率的的發生狀況。

    考慮機率的平均狀況複雜度爲:

推導過程:

這就是機率論中的加權平均值,也叫作指望值,因此平均時間複雜度全稱叫:加權平均時間複雜度或者指望時間複雜度。平均複雜度變爲,忽略係數及常量後,最終獲得加權平均時間複雜度爲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成正比時,此時空間複雜度記爲

  • public static void matrixFun(int n) {
     var matrix = new int[n][n];
     //...
    }
    複製代碼

    當算法分配的空間是一個二維數組,而且它的第一維度和第二維度的大小都和輸入規模n成正比時,此時空間複雜度記爲

5. 總結

對於時間空間的取捨,咱們就要根據具體的業務實際狀況而定,有的時候就須要犧牲時間來換空間,有的時候就須要犧牲空間來換時間,在如今這個計算機硬件性能飆升的時代,固然咱們仍是喜歡選擇犧牲空間來換時間,畢竟內存仍是有的,也不貴。而且能夠提升效率給用戶更好的體驗。

  • 什麼是時間複雜度?

    時間複雜度就是對算法運行時間長短的度量,用大O表示爲T(n) = O(f(n)) 。常見的時間複雜度從低到高的順序是:

  • 什麼是空間複雜度?

    空間複雜度是對算法運行時所佔用的臨時存儲空間的度量,用大O標識爲S(n)= O(f(n)) 。常見的空間複雜度從低到高的順序是:

6. 參考

  1. 《數據結構與算法分析》
  2. 《大話數據結構》
  3. 《漫畫算法》

能看到這裏的朋友,相信你也對學習保持着必定的熱情,以爲對你有幫助的話麻煩點個贊或在看以資鼓勵吧,有什麼問題歡迎留言或者關注我公衆號進羣交流。另外文章有理解錯誤、寫錯、說錯的地方,但願你們指正,這是對我最大的幫助,謝謝你們。

相關文章
相關標籤/搜索