日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

冰与火之歌:「时间」与「空间」复杂度

發(fā)布時(shí)間:2024/4/15 编程问答 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 冰与火之歌:「时间」与「空间」复杂度 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

算法(Algorithm)是指用來(lái)操作數(shù)據(jù)、解決程序問(wèn)題的一組方法。對(duì)于同一個(gè)問(wèn)題,使用不同的算法,也許最終得到的結(jié)果是一樣的,比如排序就有前面的十大經(jīng)典排序和幾種奇葩排序,雖然結(jié)果相同,但在過(guò)程中消耗的資源和時(shí)間卻會(huì)有很大的區(qū)別,比如快速排序與猴子排序:)。

那么我們應(yīng)該如何去衡量不同算法之間的優(yōu)劣呢?

主要還是從算法所占用的「時(shí)間」和「空間」兩個(gè)維度去考量。

  • 時(shí)間維度:是指執(zhí)行當(dāng)前算法所消耗的時(shí)間,我們通常用「時(shí)間復(fù)雜度」來(lái)描述。

  • 空間維度:是指執(zhí)行當(dāng)前算法需要占用多少內(nèi)存空間,我們通常用「空間復(fù)雜度」來(lái)描述。

冰之哀傷:時(shí)間復(fù)雜度

時(shí)間的流逝宛若寒冰的融化,散發(fā)著恐懼。

大O符號(hào)表示法

大O表示法:算法的時(shí)間復(fù)雜度通常用大O符號(hào)表述,定義為 **T[n] = O(f(n)) **。稱函數(shù)T(n)以f(n)為界或者稱T(n)受限于f(n)。

如果一個(gè)問(wèn)題的規(guī)模是n,解這一問(wèn)題的某一算法所需要的時(shí)間為T(n)。T(n)稱為這一算法的“時(shí)間復(fù)雜度”。

上面公式中用到的 Landau符號(hào)是由德國(guó)數(shù)論學(xué)家保羅·巴赫曼(Paul Bachmann)在其1892年的著作《解析數(shù)論》首先引入,由另一位德國(guó)數(shù)論學(xué)家艾德蒙·朗道(Edmund Landau)推廣。Landau符號(hào)的作用在于用簡(jiǎn)單的函數(shù)來(lái)描述復(fù)雜函數(shù)行為,給出一個(gè)上或下(確)界。在計(jì)算算法復(fù)雜度時(shí)一般只用到大O符號(hào),Landau符號(hào)體系中的小o符號(hào)、Θ符號(hào)等等比較不常用。這里的O,最初是用大寫希臘字母,但現(xiàn)在都用大寫英語(yǔ)字母O;小o符號(hào)也是用小寫英語(yǔ)字母o,Θ符號(hào)則維持大寫希臘字母Θ。

大O符號(hào)是一種算法「復(fù)雜度」的「相對(duì)」「表示」方式。

這個(gè)句子里有一些重要而嚴(yán)謹(jǐn)?shù)挠迷~:

  • 相對(duì)(relative):你只能比較相同的事物。你不能把一個(gè)做算數(shù)乘法的算法和排序整數(shù)列表的算法進(jìn)行比較。但是,比較2個(gè)算法所做的算術(shù)操作(一個(gè)做乘法,一個(gè)做加法)將會(huì)告訴你一些有意義的東西;

  • 表示(representation):大O(用它最簡(jiǎn)單的形式)把算法間的比較簡(jiǎn)化為了一個(gè)單一變量。這個(gè)變量的選擇基于觀察或假設(shè)。例如,排序算法之間的對(duì)比通常是基于比較操作(比較2個(gè)結(jié)點(diǎn)來(lái)決定這2個(gè)結(jié)點(diǎn)的相對(duì)順序)。這里面就假設(shè)了比較操作的計(jì)算開銷很大。但是,如果比較操作的計(jì)算開銷不大,而交換操作的計(jì)算開銷很大,又會(huì)怎么樣呢?這就改變了先前的比較方式;

  • 復(fù)雜度(complexity):如果排序10,000個(gè)元素花費(fèi)了我1秒,那么排序1百萬(wàn)個(gè)元素會(huì)花多少時(shí)間?在這個(gè)例子里,復(fù)雜度就是相對(duì)其他東西的度量結(jié)果。

常見的時(shí)間復(fù)雜度量級(jí)

我們先從常見的時(shí)間復(fù)雜度量級(jí)進(jìn)行大O的理解:

  • 常數(shù)階O(1)

  • 線性階O(n)

  • 平方階O(n2)

  • 對(duì)數(shù)階O(logn)

  • 線性對(duì)數(shù)階O(nlogn)

O(1)

無(wú)論代碼執(zhí)行了多少行,其他區(qū)域不會(huì)影響到操作,這個(gè)代碼的時(shí)間復(fù)雜度都是O(1)

1void?swapTwoInts(int?&a,?int?&b){
2??int?temp?=?a;
3??a?=?b;
4??b?=?temp;
5}

O(n)

在下面這段代碼,for循環(huán)里面的代碼會(huì)執(zhí)行 n 遍,因此它消耗的時(shí)間是隨著 n 的變化而變化的,因此可以用O(n)來(lái)表示它的時(shí)間復(fù)雜度。

1int?sum?(?int?n?){
2???int?ret?=?0;
3???for?(?int?i?=?0?;?i?<=?n?;?i?++){
4??????ret?+=?i;
5???}
6???return?ret;
7}

特別一提的是 c * O(n) 中的 c 可能小于 1 ,比如下面這段代碼:

1void?reverse?(?string?&s?)?{
2????int?n?=?s.size();
3????for?(int?i?=?0?;?i?<?n/2?;?i++){
4??????swap?(?s[i]?,?s[n-1-i]);
5????}
6}

O(n2)


當(dāng)存在雙重循環(huán)的時(shí)候,即把 O(n) 的代碼再嵌套循環(huán)一遍,它的時(shí)間復(fù)雜度就是 O(n2) 了。

?

1void?selectionSort(int?arr[],int?n){
2???for(int?i?=?0;?i?<?n?;?i++){
3?????int?minIndex?=?i;
4?????for?(int?j?=?i?+?1;?j?<?n?;?j++?)
5???????if?(arr[j]?<?arr[minIndex])
6???????????minIndex?=?j;
7
8?????swap?(?arr[i],?arr[minIndex]);
9???}
10}

這里簡(jiǎn)單的推導(dǎo)一下

  • 當(dāng) i = 0 時(shí),第二重循環(huán)需要運(yùn)行 (n - 1) 次
  • 當(dāng) i = 1 時(shí),第二重循環(huán)需要運(yùn)行 (n - 2) 次
  • 。。。。。。

不難得到公式:

1(n?-?1)?+?(n?-?2)?+?(n?-?3)?+?...?+?0
2=?(0?+?n?-?1)?*?n?/?2
3=?O?(n?^2)

當(dāng)然并不是所有的雙重循環(huán)都是 O(n2),比如下面這段輸出 30n 次 Hello,五分鐘學(xué)算法:)的代碼。

1void?printInformation?(int?n?){
2???for?(int?i?=?1?;?i?<=?n?;?i++)
3????????for?(int?j?=?1?;?j?<=?30?;?j?++)
4???????????cout<<?"Hello,五分鐘學(xué)算法:)"<<?endl;
5}

O(logn)

1int?binarySearch(?int?arr[],?int?n?,?int?target){
2??int?l?=?0,?r?=?n?-?1;
3??while?(?l?<=?r)?{
4????int?mid?=?l?+?(r?-?l)?/?2;
5????if?(arr[mid]?==?target)?return?mid;
6????if?(arr[mid]?>?target?)?r?=?mid?-?1;
7????else?l?=?mid?+?1;
8??}
9??return?-1;
10}

在二分查找法的代碼中,通過(guò)while循環(huán),成 2 倍數(shù)的縮減搜索范圍,也就是說(shuō)需要經(jīng)過(guò) log2^n 次即可跳出循環(huán)。

同樣的還有下面兩段代碼也是 O(logn) 級(jí)別的時(shí)間復(fù)雜度。

1??//?整形轉(zhuǎn)成字符串
2??string?intToString?(?int?num?){
3???string?s?=?"";
4???//?n?經(jīng)過(guò)幾次“除以10”的操作后,等于0
5???while?(num?){
6????s?+=?'0'?+?num%10;
7????num?/=?10;
8???}
9???reverse(s)
10???return?s;
11??}
1void?hello?(int?n?)?{
2???//?n?除以幾次?2?到?1
3???for?(?int?sz?=?1;?sz?<?n?;?sz?+=?sz)?
4?????for?(int?i?=?1;?i?<?n;?i++)
5????????cout<<?"Hello,五分鐘學(xué)算法:)"<<?endl;
6}

O(nlogn)

將時(shí)間復(fù)雜度為O(logn)的代碼循環(huán)N遍的話,那么它的時(shí)間復(fù)雜度就是 n * O(logn),也就是了O(nlogn)。

1void?hello?(){
2??for(?m?=?1?;?m?<?n?;?m++){
3????i?=?1;
4????while(?i?<?n?){
5????????i?=?i?*?2;
6????}
7???}
8}

不常見的時(shí)間復(fù)雜度

下面來(lái)分析一波另外幾種復(fù)雜度: 遞歸算法的時(shí)間復(fù)雜度(recursive algorithm time complexity),最好情況時(shí)間復(fù)雜度(best case time complexity)、最壞情況時(shí)間復(fù)雜度(worst case time complexity)、平均時(shí)間復(fù)雜度(average case time complexity)和均攤時(shí)間復(fù)雜度(amortized time complexity)。

遞歸算法的時(shí)間復(fù)雜度

如果遞歸函數(shù)中,只進(jìn)行一次遞歸調(diào)用,遞歸深度為depth;

在每個(gè)遞歸的函數(shù)中,時(shí)間復(fù)雜度為T;

則總體的時(shí)間復(fù)雜度為O(T * depth)

在前面的學(xué)習(xí)中,歸并排序 與 快速排序 都帶有遞歸的思想,并且時(shí)間復(fù)雜度都是O(nlogn) ,但并不是有遞歸的函數(shù)就一定是 O(nlogn) 級(jí)別的。從以下兩種情況進(jìn)行分析。

① 遞歸中進(jìn)行一次遞歸調(diào)用的復(fù)雜度分析
二分查找法

1int?binarySearch(int?arr[],?int?l,?int?r,?int?target){
2????if(?l?>?r?)?return?-1;
3
4????int?mid?=?l?+?(r-l)/2;?
5????if(?arr[mid]?==?target?)?return?mid;??
6????else?if(?arr[mid]?>?target?)?
7????return?binarySearch(arr,?l,?mid-1,?target);????//?左邊?
8????else
9????return?binarySearch(arr,?mid+1,?r,?target);???//?右邊?
10}

比如在這段二分查找法的代碼中,每次在 [ l , r ] 范圍中去查找目標(biāo)的位置,如果中間的元素 arr[mid] 不是 target,那么判斷 arr[mid]是比 target 大 還是 小 ,進(jìn)而再次調(diào)用 binarySearch這個(gè)函數(shù)。

在這個(gè)遞歸函數(shù)中,每一次沒有找到target時(shí),要么調(diào)用 左邊 的 binarySearch函數(shù),要么調(diào)用 右邊 的 binarySearch函數(shù)。也就是說(shuō)在此次遞歸中,最多調(diào)用了一次遞歸調(diào)用而已。根據(jù)數(shù)學(xué)知識(shí),需要log2n次才能遞歸到底。因此,二分查找法的時(shí)間復(fù)雜度為 O(logn)。

求和

1int?sum?(int?n)?{
2??if?(n?==?0)?return?0;
3??return?n?+?sum(?n?-?1?)
4}

在這段代碼中比較容易理解遞歸深度隨輸入 n 的增加而線性遞增,因此時(shí)間復(fù)雜度為 O (n)。

求冪

1//遞歸深度:logn
2//時(shí)間復(fù)雜度:O(logn)
3double?pow(?double?x,?int?n){
4??if?(n?==?0)?return?1.0;
5
6??double?t?=?pow(x,n/2);
7??if?(n?%2)?return?x*t*t;
8??return?t?*?t;
9}

遞歸深度為 logn,因?yàn)槭乔笮枰?2 多少次才能到底。

② 遞歸中進(jìn)行多次遞歸調(diào)用的復(fù)雜度分析

遞歸算法中比較難計(jì)算的是多次遞歸調(diào)用。

先看下面這段代碼,有兩次遞歸調(diào)用。

1//?O(2^n)?指數(shù)級(jí)別的數(shù)量級(jí),后續(xù)動(dòng)態(tài)規(guī)劃的優(yōu)化點(diǎn)
2int?f(int?n){
3?if?(n?==?0)?return?1;
4?return?f(n-1)?+?f(n?-?1);
5}

遞歸樹中節(jié)點(diǎn)數(shù)就是代碼計(jì)算的調(diào)用次數(shù)。

比如 當(dāng) n = 3 時(shí),調(diào)用次數(shù)計(jì)算公式為

1 + 2 + 4 + 8 = 15

一般的,調(diào)用次數(shù)計(jì)算公式為

2^0 + 2^1 + 2^2 + …… + 2^n
= 2^(n+1) - 1
= O(2^n)

與之有所類似的是 歸并排序 的遞歸樹,區(qū)別點(diǎn)在于

  • 1. 上述例子中樹的深度為 n,而 歸并排序 的遞歸樹深度為logn。
  • 2. 上述例子中每次處理的數(shù)據(jù)規(guī)模是一樣的,而在 歸并排序 中每個(gè)節(jié)點(diǎn)處理的數(shù)據(jù)規(guī)模是逐漸縮小的

因此,在如 歸并排序 等排序算法中,每一層處理的數(shù)據(jù)量為 O(n) 級(jí)別,同時(shí)有 logn 層,時(shí)間復(fù)雜度便是 O(nlogn)。

最好、最壞情況時(shí)間復(fù)雜度


最好、最壞情況時(shí)間復(fù)雜度指的是特殊情況下的時(shí)間復(fù)雜度。

?

動(dòng)圖表明的是在數(shù)組 array 中尋找變量 x 第一次出現(xiàn)的位置,若沒有找到,則返回 -1;否則返回位置下標(biāo)。

1int?find(int[]?array,?int?n,?int?x)?{
2??for?(??int?i?=?0?;?i?<?n;?i++)?{
3????if?(array[i]?==?x)?{
4????????return?i;
5????????break;
6????}
7??}
8??return?-1;
9}

在這里當(dāng)數(shù)組中第一個(gè)元素就是要找的 x 時(shí),時(shí)間復(fù)雜度是 O(1);而當(dāng)最后一個(gè)元素才是 x 時(shí),時(shí)間復(fù)雜度則是 O(n)。

最好情況時(shí)間復(fù)雜度就是在最理想情況下執(zhí)行代碼的時(shí)間復(fù)雜度,它的時(shí)間是最短的;最壞情況時(shí)間復(fù)雜度就是在最糟糕情況下執(zhí)行代碼的時(shí)間復(fù)雜度,它的時(shí)間是最長(zhǎng)的。

平均情況時(shí)間復(fù)雜度

最好、最壞時(shí)間復(fù)雜度反應(yīng)的是極端條件下的復(fù)雜度,發(fā)生的概率不大,不能代表平均水平。那么為了更好的表示平均情況下的算法復(fù)雜度,就需要引入平均時(shí)間復(fù)雜度。

平均情況時(shí)間復(fù)雜度可用代碼在所有可能情況下執(zhí)行次數(shù)的加權(quán)平均值表示。

還是以 find 函數(shù)為例,從概率的角度看, x 在數(shù)組中每一個(gè)位置的可能性是相同的,為 1 / n。那么,那么平均情況時(shí)間復(fù)雜度就可以用下面的方式計(jì)算:

((1 + 2 + … + n) / n + n) / 2 = (3n + 1) / 4

find 函數(shù)的平均時(shí)間復(fù)雜度為 O(n)。

均攤復(fù)雜度分析

我們通過(guò)一個(gè)動(dòng)態(tài)數(shù)組的 push_back 操作來(lái)理解 均攤復(fù)雜度

1template?<typename?T>
2class?MyVector{
3private:
4????T*?data;
5????int?size;???????//?存儲(chǔ)數(shù)組中的元素個(gè)數(shù)
6????int?capacity;???//?存儲(chǔ)數(shù)組中可以容納的最大的元素個(gè)數(shù)
7????//?復(fù)雜度為?O(n)
8????void?resize(int?newCapacity){
9????????T?*newData?=?new?T[newCapacity];
10????????for(?int?i?=?0?;?i?<?size?;?i?++?){
11??????????????newData[i]?=?data[i];
12????????????}
13????????data?=?newData;
14????????capacity?=?newCapacity;
15????}
16public:
17????MyVector(){
18????????data?=?new?T[100];
19????????size?=?0;
20????????capacity?=?100;
21????}
22????//?平均復(fù)雜度為?O(1)
23????void?push_back(T?e){
24????????if(size?==?capacity)
25????????????resize(2?*?capacity);
26????????data[size++]?=?e;
27????}
28????//?平均復(fù)雜度為?O(1)
29????T?pop_back(){
30????????size?--;
31????????return?data[size];
32????}
33
34};

push_back實(shí)現(xiàn)的功能是往數(shù)組的末尾增加一個(gè)元素,如果數(shù)組沒有滿,直接往后面插入元素;如果數(shù)組滿了,即 size == capacity ,則將數(shù)組擴(kuò)容一倍,然后再插入元素。

例如,數(shù)組長(zhǎng)度為 n,則前 n 次調(diào)用 push_back 復(fù)雜度都為 O(1) 級(jí)別;在第 n + 1 次則需要先進(jìn)行 n 次元素轉(zhuǎn)移操作,然后再進(jìn)行 1 次插入操作,復(fù)雜度為 O(n)。

因此,平均來(lái)看:對(duì)于容量為 n 的動(dòng)態(tài)數(shù)組,前面添加元素需要消耗了 1 * n 的時(shí)間,擴(kuò)容操作消耗 n 時(shí)間 ,
總共就是 2 * n 的時(shí)間,因此均攤時(shí)間復(fù)雜度為 O(2n / n) = O(2),也就是 O(1) 級(jí)別了。

可以得出一個(gè)比較有意思的結(jié)論:一個(gè)相對(duì)比較耗時(shí)的操作,如果能保證它不會(huì)每次都被觸發(fā),那么這個(gè)相對(duì)比較耗時(shí)的操作,它所相應(yīng)的時(shí)間是可以分?jǐn)偟狡渌牟僮髦衼?lái)的。

火之晨曦:空間復(fù)雜度

????,到處都是?

一個(gè)程序的空間復(fù)雜度是指運(yùn)行完一個(gè)程序所需內(nèi)存的大小。利用程序的空間復(fù)雜度,可以對(duì)程序的運(yùn)行所需要的內(nèi)存多少有個(gè)預(yù)先估計(jì)。一個(gè)程序執(zhí)行時(shí)除了需要存儲(chǔ)空間和存儲(chǔ)本身所使用的指令、常數(shù)、變量和輸入數(shù)據(jù)外,還需要一些對(duì)數(shù)據(jù)進(jìn)行操作的工作單元和存儲(chǔ)一些為現(xiàn)實(shí)計(jì)算所需信息的輔助空間。程序執(zhí)行時(shí)所需存儲(chǔ)空間包括以下兩部分:

(1) 固定部分,這部分空間的大小與輸入/輸出的數(shù)據(jù)的個(gè)數(shù)多少、數(shù)值無(wú)關(guān)。主要包括指令空間(即代碼空間)、數(shù)據(jù)空間(常量、簡(jiǎn)單變量)等所占的空間。這部分屬于靜態(tài)空間。

(2) 可變空間,這部分空間的主要包括動(dòng)態(tài)分配的空間,以及遞歸棧所需的空間等。這部分的空間大小與算法有關(guān)。

一個(gè)算法所需的存儲(chǔ)空間用f(n)表示。S(n)=O(f(n)),其中n為問(wèn)題的規(guī)模,S(n)表示空間復(fù)雜度。

空間復(fù)雜度可以理解為除了原始序列大小的內(nèi)存,在算法過(guò)程中用到的額外的存儲(chǔ)空間。

以二叉查找樹為例,舉例說(shuō)明二叉排序樹的查找性能。

平衡二叉樹

如果二叉樹的是以紅黑樹等平衡二叉樹實(shí)現(xiàn)的,則 n 個(gè)節(jié)點(diǎn)的二叉排序樹的高度為 log2n+1 ,其查找效率為O(Log2n),近似于折半查找。

列表二叉樹

如果二叉樹退變?yōu)榱斜砹?#xff0c;則 n 個(gè)節(jié)點(diǎn)的高度或者說(shuō)是長(zhǎng)度變?yōu)榱薾,查找效率為O(n),變成了順序查找。

一般二叉樹

介于「列表二叉樹」與「平衡二叉樹」之間,查找性能也在O(Log2n)到O(n)之間。

冰火交融

對(duì)于一個(gè)算法,其時(shí)間復(fù)雜度和空間復(fù)雜度往往是相互影響的。

比如說(shuō),要判斷某某年是不是閏年:

  • 1. 可以編寫一個(gè)算法來(lái)計(jì)算,這也就意味著,每次給一個(gè)年份,都是要通過(guò)計(jì)算得到是否是閏年的結(jié)果。
  • 2. 還有另一個(gè)辦法就是,事先建立一個(gè)有 5555 個(gè)元素的數(shù)組(年數(shù)比現(xiàn)實(shí)多就行),然后把所有的年份按下標(biāo)的數(shù)字對(duì)應(yīng),如果是閏年,此數(shù)組項(xiàng)的值就是1,如果不是值為0。這樣,所謂的判斷某一年是否是閏年,就變成了查找這個(gè)數(shù)組的某一項(xiàng)的值是多少的問(wèn)題。此時(shí),我們的運(yùn)算是最小化了,但是硬盤上或者內(nèi)存中需要存儲(chǔ)這 5555 個(gè) 0 和 1 。

這就是典型的使用空間換時(shí)間的概念。

當(dāng)追求一個(gè)較好的時(shí)間復(fù)雜度時(shí),可能會(huì)使空間復(fù)雜度的性能變差,即可能導(dǎo)致占用較多的存儲(chǔ)空間;
反之,求一個(gè)較好的空間復(fù)雜度時(shí),可能會(huì)使時(shí)間復(fù)雜度的性能變差,即可能導(dǎo)致占用較長(zhǎng)的運(yùn)行時(shí)間。

另外,算法的所有性能之間都存在著或多或少的相互影響。因此,當(dāng)設(shè)計(jì)一個(gè)算法(特別是大型算法)時(shí),要綜合考慮算法的各項(xiàng)性能,算法的使用頻率,算法處理的數(shù)據(jù)量的大小,算法描述語(yǔ)言的特性,算法運(yùn)行的機(jī)器系統(tǒng)環(huán)境等各方面因素,才能夠設(shè)計(jì)出比較好的算法。

喜歡就點(diǎn)個(gè)贊唄:)

轉(zhuǎn)載于:https://www.cnblogs.com/fivestudy/p/10131495.html

總結(jié)

以上是生活随笔為你收集整理的冰与火之歌:「时间」与「空间」复杂度的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。