【数据结构】时间复杂度和空间复杂度的计算
目錄
- 一、數(shù)據(jù)結(jié)構(gòu)
- 1、什么是數(shù)據(jù)結(jié)構(gòu)
- 2、什么是算法
- 3、數(shù)據(jù)結(jié)構(gòu)和算法的重要性
- 4、如何學(xué)好數(shù)據(jù)結(jié)構(gòu)和算法
- 二、算法效率
- 三、時(shí)間復(fù)雜度
- 1、時(shí)間復(fù)雜度的概念
- 2、時(shí)間復(fù)雜度的表示方法
- 3、算法復(fù)雜度的三種情況
- 4、簡(jiǎn)單時(shí)間復(fù)雜度的計(jì)算
- 5、復(fù)雜時(shí)間復(fù)雜度的計(jì)算
- 五、不同時(shí)間復(fù)雜度效率的比較
- 四、空間復(fù)雜度
- 1、空間復(fù)雜度的概念
- 2、空間復(fù)雜度的計(jì)算方法
- 3、常見(jiàn)空間復(fù)雜度的計(jì)算
- 五、總結(jié)
一、數(shù)據(jù)結(jié)構(gòu)
1、什么是數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)(Data Structure)是計(jì)算機(jī)存儲(chǔ)、組織數(shù)據(jù)的方式,指相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合。簡(jiǎn)單來(lái)說(shuō),數(shù)據(jù)結(jié)構(gòu)就是對(duì)數(shù)據(jù)進(jìn)行管理(增刪查改)的一系列操作。
數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)庫(kù)的作用很相似,二者的區(qū)別在于管理的位置不同:當(dāng)數(shù)據(jù)量很大時(shí),數(shù)據(jù)一般都會(huì)存放在磁盤中,此時(shí)我們用數(shù)據(jù)庫(kù)進(jìn)行管理;當(dāng)數(shù)據(jù)量相對(duì)較小時(shí),我們用數(shù)據(jù)結(jié)構(gòu)來(lái)管理。
2、什么是算法
算法 (Algorithm) 就是定義良好的計(jì)算過(guò)程,他取一個(gè)或一組的值為輸入,并產(chǎn)生出一個(gè)或一組值作為輸出。簡(jiǎn)單來(lái)說(shuō)算法就是一系列的計(jì)算步驟,用來(lái)將輸入數(shù)據(jù)轉(zhuǎn)化成輸出結(jié)果。
數(shù)據(jù)結(jié)構(gòu)和算法是相輔相成的,二者是我中有你、你中有我的關(guān)系:在一個(gè)數(shù)據(jù)結(jié)構(gòu)中可能會(huì)用到算法來(lái)優(yōu)化,一個(gè)算法中也可能用到數(shù)據(jù)結(jié)構(gòu)來(lái)組織數(shù)據(jù)。
3、數(shù)據(jù)結(jié)構(gòu)和算法的重要性
在校園招聘的筆試:
目前校園招聘筆試一般采用Online Judge (在線OJ) 形式, 一般都是20-30道選擇題+2道編程題,或者3-4道 編程題。
可以看出,現(xiàn)在公司對(duì)我們代碼能力的要求是越來(lái)越高了,大廠筆試中幾乎全是算法題而且難度大,中小廠的筆試中才會(huì)有選擇題。算法不僅筆試中考察,面試中面試官基本都會(huì)讓現(xiàn)場(chǎng)寫代碼。而算法能力短期內(nèi)無(wú)法快速提高,至少需要持續(xù)半年以上算法訓(xùn)練積累,否則真正校招時(shí) 試會(huì)很艱難,因此算法要早早準(zhǔn)備。
在校園招聘的面試中:
在面試環(huán)節(jié),數(shù)據(jù)結(jié)構(gòu)和算法也是被經(jīng)常問(wèn)到的一部分,大家在牛客、LeetCode的面經(jīng)中也能夠發(fā)現(xiàn)這一點(diǎn),比如如下的一些問(wèn)題:
- 怎么用兩個(gè)棧實(shí)現(xiàn)一個(gè)隊(duì)列?
- 如何判斷兩個(gè)鏈表是否相交?
- Vector和數(shù)組的區(qū)別?
- 紅黑樹的原理、時(shí)間復(fù)雜度等?
- map和set底層原理?
- 快速排序思想是什么?
- Hashmap的原理?
在未來(lái)的工作中:
這里我引用網(wǎng)上的一篇文章:學(xué)好算法對(duì)一個(gè)程序員來(lái)說(shuō)是必須的嗎?如果是,至少應(yīng)該學(xué)到哪種程度
4、如何學(xué)好數(shù)據(jù)結(jié)構(gòu)和算法
關(guān)于這個(gè)問(wèn)題的答案,我想大家都知道,要想學(xué)好數(shù)據(jù)結(jié)構(gòu)和算法,除了多練還是多練,至少我們需要把《劍指offer》《程序員代碼面試指南》全部刷完,LeetCode至少刷幾百道題,然后就是注意在做具體題目之前要先畫圖分析,理清思路之后再下手。
劍指offer 程序員面試經(jīng)典 LeetCode OJ 題庫(kù)
二、算法效率
算法的復(fù)雜度
算法效率分析分為兩種:第一種是時(shí)間效率,第二種是空間效率。時(shí)間效率被稱為時(shí)間復(fù)雜度,而空間效率被稱作空間復(fù)雜度。
時(shí)間復(fù)雜度主要衡量的是一個(gè)算法的運(yùn)行速度,而空間復(fù)雜度主要衡量一個(gè)算法所需要的額外空間。
在計(jì)算機(jī)發(fā)展的早期,計(jì)算機(jī)的存儲(chǔ)容量很小,所以對(duì)空間復(fù)雜度很是在乎;但是經(jīng)過(guò)計(jì)算機(jī)行業(yè)的迅速發(fā)展,計(jì)算機(jī)的存儲(chǔ)容量已經(jīng)達(dá)到了很高的程度;所以我們?nèi)缃褚呀?jīng)不需要再特別關(guān)注一個(gè)算法的空間復(fù)雜度,而更注重于時(shí)間復(fù)雜度。
算法復(fù)雜度在校招中的考察
三、時(shí)間復(fù)雜度
1、時(shí)間復(fù)雜度的概念
時(shí)間復(fù)雜度的定義:在計(jì)算機(jī)科學(xué)中,算法的時(shí)間復(fù)雜度是一個(gè)函數(shù),它定量描述了該算法的運(yùn)行時(shí)間。(這里的函數(shù)指的是數(shù)學(xué)中的函數(shù),而不是我們C語(yǔ)言中的函數(shù))
一個(gè)算法執(zhí)行所耗費(fèi)的時(shí)間,從理論上說(shuō)是不能算出來(lái)的,因?yàn)橹挥挟?dāng)我們把程序放在機(jī)器上跑起來(lái),才能知道具體時(shí)間。但是我們需要每個(gè)算法都上機(jī)測(cè)試嗎?是可以都上機(jī)測(cè)試,但是這很麻煩,所以才有了時(shí)間復(fù)雜度這個(gè)分析方式。
一個(gè)算法所花費(fèi)的時(shí)間與其中語(yǔ)句的執(zhí)行次數(shù)成正比例,算法中的基本操作的執(zhí)行次數(shù),為算法的時(shí)間復(fù)雜度。
2、時(shí)間復(fù)雜度的表示方法
我們計(jì)算時(shí)間復(fù)雜度時(shí)不是計(jì)算算法運(yùn)行的具體次數(shù),而是用大O的漸進(jìn)表示法來(lái)計(jì)算,其具體計(jì)算方法如下:
- 用常數(shù)1取代運(yùn)行時(shí)間中的所有加法常數(shù)。
- 在修改后的運(yùn)行次數(shù)函數(shù)中,只保留最高階項(xiàng)。
- 如果最高階項(xiàng)存在且不是1,則去除與這個(gè)項(xiàng)目相乘的常數(shù)。
3、算法復(fù)雜度的三種情況
算法的復(fù)雜度分為三種情況:
- 最壞情況:任意輸入規(guī)模的最大運(yùn)行次數(shù)(上界)
- 平均情況:任意輸入規(guī)模的期望運(yùn)行次數(shù)
- 最好情況:任意輸入規(guī)模的最小運(yùn)行次數(shù)(下界)
例如:在一個(gè)長(zhǎng)度為N數(shù)組中搜索一個(gè)數(shù)據(jù)x
最好情況:1次找到
平均情況:N/2次找到
最壞情況:N次找到
平均情況:N/2次找到
在實(shí)際中一般情況關(guān)注的是算法的最壞運(yùn)行情況,所以數(shù)組中搜索數(shù)據(jù)時(shí)間復(fù)雜度為O(N)。
4、簡(jiǎn)單時(shí)間復(fù)雜度的計(jì)算
例1:
void Func(int N) { int count = 0; for (int k = 0; k < 100; ++ k) {++count; } printf("%d\n", count); }上面程序具體執(zhí)行的次數(shù):100
用大O的漸進(jìn)表示法得出時(shí)間復(fù)雜度:O(1) (用常數(shù)1取代運(yùn)行時(shí)間中的所有加法常數(shù)。)
例2:
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--){++count;}printf("%d\n", count); }上面程序具體執(zhí)行的次數(shù):N * N + 2*N + 10
用大O的漸進(jìn)表示法得出時(shí)間復(fù)雜度:O(N^2) (只保留最高階項(xiàng))
例3:
void Func2(int N) { int count = 0; for (int k = 0; k < 2 * N ; ++ k) {++count; } int M = 10; while (M--) {++count; } printf("%d\n", count); }上面程序具體執(zhí)行的次數(shù):2*N + 10
用大O的漸進(jìn)表示法得出時(shí)間復(fù)雜度:O(N) (如果最高階項(xiàng)存在且不是1,則去除與這個(gè)項(xiàng)目相乘的常數(shù)
5、復(fù)雜時(shí)間復(fù)雜度的計(jì)算
(1)冒泡排序的時(shí)間復(fù)雜度
void BubbleSort(int arr[], int n) {int i = 0;int j = 0;for (i = 0; i < n - 1; i++){for (j = 0; j < n - i - 1; j++){if (arr[j] > arr[j + 1]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}} }具體執(zhí)行次數(shù):時(shí)間復(fù)雜度計(jì)算時(shí)以最壞情況為準(zhǔn),則假設(shè)數(shù)組開(kāi)始是逆序的,那么第一次排序執(zhí)行 n-1 次,第二次排序執(zhí)行 n-2 次,第三次執(zhí)行 n-3 次 … …,直到最后達(dá)成有序,所以冒泡排序的具體執(zhí)行次數(shù)是一個(gè)等差數(shù)列,具體次數(shù) = (首項(xiàng)+尾項(xiàng))/2*項(xiàng)數(shù) = (N^2-N)/2
用大O的漸進(jìn)表示法得出時(shí)間復(fù)雜度:O(N^2)
(2)二分查找的時(shí)間復(fù)雜度
int BinarySearch(int arr[], int n, int x) //n元素個(gè)數(shù) x:要查找的數(shù) {int left = 0;int right = n - 1;while (left < right){int mid = (left + right) / 2;if (arr[mid] > x){right = mid - 1; //中間元素比x大就挪動(dòng)right下標(biāo)}else if (arr[mid] < x){left = mid + 1; //中間元素比x小就挪動(dòng)left下標(biāo)}elsereturn mid; //找到就返回該元素所在的下標(biāo)}return 0; //找不到就返回0 }具體執(zhí)行次數(shù):和上面一樣,這里考慮最壞情況,即數(shù)組中沒(méi)有想找的元素,數(shù)組會(huì)從中間開(kāi)始查找,每次排除掉一半的元素,直到把所有元素排除完,第一次排除后剩下 1/2 的元素,第二次排除后剩下 1/4 元素,第三次排除后剩下 1/8 元素 … …,設(shè)元素個(gè)數(shù)為N,查找次數(shù)為X,則 X * (?)^N = 1 -> (?)^N = X -> 具體次數(shù):X = log2N
用大O的漸進(jìn)表示法得出時(shí)間復(fù)雜度:O(logN)
注:因?yàn)樵阪I盤上無(wú)法表示出log的底數(shù),所有在時(shí)間復(fù)雜度中把log的底數(shù)2省略掉了,直接用logN表示log2N的時(shí)間復(fù)雜度。
(3)階乘遞歸的時(shí)間復(fù)雜度
long long Factorial(int N) {return N < 2 ? N : Factorial(N - 1) * N; }具體次數(shù):這里 n 調(diào)用 n-1 , n-1 調(diào)用 n-2 …,直到 n = 1,所以一共執(zhí)行了 n-1 次。
用大O的漸進(jìn)表示法得出時(shí)間復(fù)雜度:O(N)
以五的階乘示例:
(4)斐波那契遞歸的時(shí)間復(fù)雜度
long long Fibonacci(size_t N) {return N < 2 ? N : Fibonacci(N - 1) + Fibonacci(N - 2); }具體次數(shù):以上圖為例,我們可以看到在數(shù)值大于2的時(shí)候,每一層的調(diào)用次數(shù)是以2的指數(shù)形式增長(zhǎng)的,是一個(gè)等比數(shù)列。
用大O的漸進(jìn)表示法得出時(shí)間復(fù)雜度為:O(2^N)
五、不同時(shí)間復(fù)雜度效率的比較
我們可以看到當(dāng)測(cè)試數(shù)據(jù)很大時(shí) O(logN) 和 O(1) 的效率幾乎是一樣的,所以二分查找是一種效率很高的算法,但是它也有一個(gè)缺陷,那就是它操作的數(shù)組元素必須是有序的。
四、空間復(fù)雜度
1、空間復(fù)雜度的概念
空間復(fù)雜度也是一個(gè)數(shù)學(xué)表達(dá)式,是對(duì)一個(gè)算法在運(yùn)行過(guò)程中額外占用存儲(chǔ)空間大小的量度 。
空間復(fù)雜度不是程序占用了多少bytes的空間,因?yàn)檫@個(gè)也沒(méi)太大意義,空間復(fù)雜度算的是變量的個(gè)數(shù)。 空間復(fù)雜度計(jì)算規(guī)則基本跟時(shí)間復(fù)雜度類似,也使用大O的漸進(jìn)表示法。
注意:函數(shù)運(yùn)行時(shí)所需要的棧空間(存儲(chǔ)參數(shù)、局部變量、一些寄存器信息等)在編譯期間已經(jīng)確定好了,因此空間復(fù)雜度主要通過(guò)函數(shù)在運(yùn)行時(shí)候顯式申請(qǐng)的額外空間來(lái)確定。
2、空間復(fù)雜度的計(jì)算方法
空間復(fù)雜度的計(jì)算方法和時(shí)間復(fù)雜度非常相似,且都是用大O的漸進(jìn)表示法表示。 具體計(jì)算方法如下:
- 用常數(shù)1取代運(yùn)行過(guò)程中定義的常數(shù)個(gè)變量。
- 在修改后的運(yùn)行次數(shù)函數(shù)中,只保留最高階項(xiàng)。
- 如果最高階項(xiàng)存在且不是1,則去除與這個(gè)項(xiàng)目相乘的常數(shù)。
3、常見(jiàn)空間復(fù)雜度的計(jì)算
(1)冒泡排序的空間復(fù)雜度
void BubbleSort(int arr[], int n) {int i = 0;int j = 0;for (i = 0; i < n - 1; i++){for (j = 0; j < n - i - 1; j++){if (arr[j] > arr[j + 1]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}} }這里我們?cè)谘h(huán)外部定義了兩個(gè)變量,然后在循環(huán)內(nèi)部又定義了一個(gè)變量;可能有的同學(xué)會(huì)認(rèn)為temp變量因?yàn)樵谘h(huán)內(nèi)部,每次進(jìn)入循環(huán)都會(huì)被重新定義,所以空間復(fù)雜度為N^2,其實(shí)不是的;
我們知道雖然時(shí)間是累積的,一去不復(fù)返,但是空間是不累積的,我們可以重復(fù)使用;對(duì)于我們的temp變量來(lái)說(shuō),每次進(jìn)入if這個(gè)局部范圍時(shí)開(kāi)辟空間,離開(kāi)這個(gè)局部范圍時(shí)空間銷毀,下一次在進(jìn)入時(shí)又重新開(kāi)辟空間,出去又再次銷毀;所以其實(shí)從始至終temp都只占用了一個(gè)空間;
所以上面一共一共定義了三個(gè)變量,用大O的漸進(jìn)表示法得到空間復(fù)雜度為O(1)。
(2)二分查找的空間復(fù)雜度
int BinarySearch(int arr[], int n, int x) //n元素個(gè)數(shù) x:要查找的數(shù) {int left = 0;int right = n - 1;while (left < right){int mid = (left + right) / 2;if (arr[mid] > x){right = mid - 1; //中間元素比x大就挪動(dòng)right下標(biāo)}else if (arr[mid] < x){left = mid + 1; //中間元素比x小就挪動(dòng)left下標(biāo)}elsereturn mid; //找到就返回該元素所在的下標(biāo)}return 0; //找不到就返回0 }和冒泡排序的空間復(fù)雜度一樣,這里只定義了三個(gè)(常數(shù)個(gè))變量,所以空間復(fù)雜度是O(1)。
(3)階乘遞歸的空間復(fù)雜度
long long Fac(int N) {return N < 2 ? N : Factorial(N - 1) * N; }我們知道,每次函數(shù)調(diào)用開(kāi)始時(shí)都會(huì)在棧區(qū)上形成自己的函數(shù)棧幀,調(diào)用結(jié)束時(shí)函數(shù)棧幀銷毀;
對(duì)于上面的遞歸來(lái)說(shuō):只有當(dāng) N < 2 的時(shí)候函數(shù)才開(kāi)始返回,而在這之前所形成的 Fac(N) Fac(N-1) Fac(N-2) … 這些函數(shù)的函數(shù)棧幀在返回之前都不會(huì)釋放,而是一直存在,知道函數(shù)一步一步開(kāi)始返回的時(shí)候開(kāi)辟的空間才會(huì)被逐漸釋放。所以在計(jì)算遞歸類空間復(fù)雜度度時(shí),我們?cè)谝獾氖沁f歸的深度;
這里調(diào)用的遞歸深度為 n - 1(遞歸 n - 1 次),所以空間復(fù)雜度為O(N)。
(4)斐波那契遞歸的空間復(fù)雜度
long long Fibonacci(size_t N) {return N < 2 ? N : Fibonacci(N - 1) + Fibonacci(N - 2); }首先就,斐波那契是逐個(gè)分支進(jìn)行遞歸的,以上圖為例,它會(huì)先遞歸6-5-4-3-2-1,再遞歸6-5-4-3-2,再遞歸6-5-4-2,以此類推,直到把最后一個(gè)分支遞歸完;
其次,空間是不會(huì)累積的,所以盡管我們同一個(gè)函數(shù)的函數(shù)棧幀會(huì)被開(kāi)辟很多次,但是它仍然只計(jì)入一次開(kāi)辟的空間復(fù)雜度。
所以遞歸調(diào)用開(kāi)遞歸的深度,這里的空間復(fù)雜度為O(N)。
五、總結(jié)
- 時(shí)間復(fù)雜度和空間復(fù)雜度都是用大O的漸進(jìn)表示法來(lái)表示。
- 時(shí)間復(fù)雜度看運(yùn)算執(zhí)行的次數(shù),空間復(fù)雜度看變量定義的個(gè)數(shù)。
- 在遞歸中,時(shí)間復(fù)雜度看調(diào)用的次數(shù),空間復(fù)雜度看調(diào)用的深度。
- 時(shí)間是累積的,一去不復(fù)返;空間是不累積的,可以重復(fù)利用。
- 冒泡排序的時(shí)間復(fù)雜度為O(N^2),空間復(fù)雜度為O(1)。
- 二分查找的時(shí)間復(fù)雜度為O(logN),空間復(fù)雜度為O(1)。
- 階乘遞歸的時(shí)間復(fù)雜度為O(N),空間復(fù)雜度為O(N)。
- 斐波那契遞歸的時(shí)間復(fù)雜度為O(2^N),空間復(fù)雜度為O(N)。
總結(jié)
以上是生活随笔為你收集整理的【数据结构】时间复杂度和空间复杂度的计算的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何爬取链家网页房源信息
- 下一篇: 500多公里上空拍摄的毕业照!你一定没见