2019年,這個不平凡的一年,中美貿易戰
、各個大廠裁人
。形成了如今互聯網行情很差,形勢很嚴峻啊。有的人說今年是互聯網過去十年中最差的一年,也多是將來十年中最好的一年。身處這樣亂世的咱們怎麼辦?我也聽不少朋友說,今年的面試都比較嚴格,特色是"要求高、薪資低"
。也常常聽見他們說某某大廠考了個手寫算法,結果當場掛了。身爲程序員的咱們,再這樣行業形勢嚴峻、競爭壓力大的狀況下,只有不斷提高自身能力,以確保在行業內能有個立錐之地
。java
《數據結構與算法》在我學生時代就是一門讓我望而止步的課程。聽着名字就感受很晦澀難懂、須要大量的數學知識作鋪墊。相信不少人也都和我同樣,上學的時候學的只知其一;不知其二,到了工做之後也不多用到就不了了之了。可是它卻成爲了你面試、尋找好的平臺的障礙。不少大廠都很看中程序員的基本功
,因此在面試中算法就編程了常考題目,爲何呢?由於基礎知識就像是一座大樓的地基,它可以決定你技術的高度
與深度
。因此通常大廠都是看中你有沒有這個技術發展的潛力。("因此你們要夯實基本功了。")
git
在我看來後端程序員應該學的有三大基礎知識"數據結構與算法"
、"計算機系統"
、"操做系統Linux"
。在這我的人都必需要手撕算法的時代,徹夜難眠的我(純屬扯淡
)決定帶領你們一塊兒學習三大基礎知識,本次開篇系列是《手撕數據結構與算法》,每個系列更完就會開啓下一個系列,你們不要着急。程序員
先放個圖,讓你們瞭解下本系列都要講的內容github
注意,注意前方高能======>(廣告植入)
web
若是你對個人這個系列感興趣能夠關注個人公衆號,帶你走上」超神之路、拿高薪offer、當上技術專家、出任個大廠、迎娶白富美、走上人生巔峯,想一想還有點小激動。」 (請容許我吹個🐂)面試
來了 來了 他來了 他帶着二維碼來了!!!算法
![]()
歡迎掃碼關注哦!!!
在咱們系統的學習數據結構與算法以前,咱們先簡單的複習幾個數學知識,相信你們也都忘的差很少了,是否是都學完了又還給老師了呢?嫑急,跟我一塊兒來複習一下。編程
指數是冪運算aⁿ(a≠0)中的一個參數,a爲底數,n爲指數,指數位於底數的右上角,冪運算表示指數個底數相乘。當n是一個正整數,aⁿ表示n個a連乘。當n=0時,aⁿ=1。《百度百科》後端
指數:就是aⁿ中的n。
底數:就是aⁿ的a
冪運算:指數個底數相乘。
冪運算公式:
數組
若是a的x次方等於N(a>0,且a不等於1),那麼數x叫作以a爲底N的對數(logarithm),記做
。其中,a叫作對數的底數,N叫作真數。《百度百科》
在計算機科學中,除非有特別的聲明,不然全部的對數都是以2爲底的。
公式:
簡單列了兩個公式,你們看看就好了,知道一下啥是對數
。
對於算法時間複雜度
,可能有的朋友可能想了,不就是估算一段代碼的執行時間嘛,咱們能夠搞個監控啊,看看一下每一個接口的耗時不就行了,何須那麼麻煩,還要分析下時間複雜度。可是這個監控屬於過後操做,只有代碼在運行時
,才能知道你寫的代碼效率
高不高,那麼如何在寫代碼的時候就評估一段代碼的執行效率呢,這個時候就須要時間複雜度來分析了。你們日常寫代碼能夠結合時間複雜度和監控作好事前
、過後
的分析,更好的優化代碼。
由於漸進時間複雜度使用大寫O來表示,因此也稱大O表示法
。例如: O(f(n))
。
常見時間複雜度:
常見時間複雜度所耗費時間從小到大依次是:
推導大O的方法:
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次/
}
}
複製代碼
讀到這裏不知道你們學會了沒有?其實分析一段代碼的時間複雜度,就找到你代碼中執行次數最多的地方,分析一下它的時間複雜度是什麼,那麼你整段代碼的時間複雜度就是什麼。以最大爲準。
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)。
算法的空間複雜度是對運行過程當中臨時佔用存儲空間大小的度量,算法空間複雜度的計算公式記做:S(n) = O(f(n))
,n爲問題規模,f(n)爲語句關於n所佔存儲空間函數。因爲空間複雜度和時間複雜度的大O表示法相同,因此咱們就簡單介紹下。
常見的空間複雜度從低到高是:
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成正比時,此時空間複雜度記爲 。
對於時間
和空間
的取捨,咱們就要根據具體的業務實際狀況而定,有的時候就須要犧牲時間來換空間,有的時候就須要犧牲空間來換時間,在如今這個計算機硬件性能飆升的時代,固然咱們仍是喜歡選擇犧牲空間來換時間,畢竟內存仍是有的,也不貴。而且能夠提升效率給用戶更好的體驗。
什麼是時間複雜度?
時間複雜度就是對算法運行時間長短的度量,用大O表示爲T(n) = O(f(n))
。常見的時間複雜度從低到高的順序是:
什麼是空間複雜度?
空間複雜度是對算法運行時所佔用的臨時存儲空間的度量,用大O標識爲S(n)= O(f(n))
。常見的空間複雜度從低到高的順序是:
能看到這裏的朋友,相信你也對學習保持着必定的熱情,以爲對你有幫助的話麻煩點個贊或在看以資鼓勵吧,有什麼問題歡迎留言或者關注我公衆號進羣交流。另外文章有理解錯誤、寫錯、說錯的地方,但願你們指正,這是對我最大的幫助,謝謝你們。