主頁 > 軟體設計 > 淺析演算法的時間復雜度和空間復雜度 (C++/python雙語實體)

淺析演算法的時間復雜度和空間復雜度 (C++/python雙語實體)

2021-10-22 16:06:44 軟體設計

如何衡量一個演算法的好壞呢?
一個演算法如果寫的十分的短,是不是就非常的好呢?

例如斐波那契數列:

C++:

#include <iostream>
#include <iomanip>
#include <cmath>
 
using namespace std;
 
#define M_SQRT5      2.2360679774997896964091736687313
#define M_1pSQRT5_2  1.6180339887498948482045868343656
 
long double Fibo(size_t n)
{
	return round((pow(M_1pSQRT5_2,n)-pow(1-M_1pSQRT5_2,n))/M_SQRT5);
}
 
int main(void)
{	
    for (long n=1;n<=100;n++)
    	cout<<n<<"\t= "<<setprecision(21)<<Fibo(n)<<endl;
 
	cout << "... ..." << endl;
 
    for (long n=1470;n<=1480;n++)
    	cout<<n<<"\t= "<<setprecision(21)<<Fibo(n)<<endl;
 
	return 0;
}

python:

def Fib(n:int)->int:
    return 1 if n<3 else Fib(n-1)+Fib(n-2)

以上代碼,c++和python的主體函式都只有一行return陳述句,一個是使用數列通項公式(又稱比內公式),一個使用遞回法;前者局限于long double的精度,后者局限于遞回層數的限制,

通常情況下,計算斐波那契數列多數會使用數列定義的遞推公式來遞回,但當n大于30時計算就非常吃力了;想要計算第100萬項的值基本上是不可能的了,

>>> from time import time
>>> for i in range(30,40):
	t=time();n=Fib(i);print(i,time()-t)

	
30 0.42186927795410156
31 0.6874938011169434
32 1.1093668937683105
33 1.7812433242797852
34 2.8906190395355225
35 4.640621185302734
36 7.48436975479126
37 12.109369277954102
38 19.6406192779541
39 31.74999499320984

用Fib(n-1)+Fib(n-2)遞回只能一項項往前推,太慢了;我樣可以這樣改進,用斐波數列的性質(如下所示)來遞回,效率明顯提升:計算第10萬項秒殺,第100萬項20秒以內,

由 F(1)=F(2)=1; F(n)=F(n-1)+F(n-2) 推匯出:

F(2n)=F(n+1)*F(n)+F(n)*F(n-1) ; F(2n+1)=F2(n+1)+F2(n)

>>> def Fib(n:int)->int:
    if n<3: return 1
    if n%2: return Fib(n//2)**2+Fib(n//2+1)**2
    return Fib(n//2)*(Fib(n//2+1)+Fib(n//2-1))

>>> from time import time
>>> t=time();n=Fib(100);print(time()-t)
0.0
>>> t=time();n=Fib(1000);print(time()-t)
0.015600204467773438
>>> t=time();n=Fib(10000);print(time()-t)
0.062400102615356445
>>> t=time();n=Fib(100000);print(time()-t)
0.9536018371582031
>>> t=time();n=Fib(1000000);print(time()-t)
18.566032886505127

此函式已知項只有F(1)、F(2)兩項,若增加已知項的項數也能提升一點效率,計算第100萬項的值只要3秒不到:

>>> def Fib(n:int)->int:
    if n<11: return [0,1,1,2,3,5,8,13,21,34,55][n]
    t=n//2
    if n%2: return Fib(t)**2+Fib(t+1)**2
    return Fib(t)*(Fib(t+1)+Fib(t-1))

>>> from time import time
>>> t=time();n=Fib(1000);print(time()-t)
0.0
>>> t=time();n=Fib(10000);print(time()-t)
0.017600059509277344
>>> t=time();n=Fib(100000);print(time()-t)
0.22040081024169922
>>> t=time();n=Fib(1000000);print(time()-t)
2.76320481300354
>>> 

那么我們要用什么方式去衡量一個演算法的好壞,所以我們引進了時間復雜度和空間復雜度, 時間復雜度主要衡量一個演算法的運行快慢,而空間復雜度主要衡量一個演算法運行所占用空間,

時間復雜度

時間復雜度的概念

衡量一個演算法的運行時間快慢也就是時間復雜度,那么計量的單位是什么?如果用我們常用的時間單位例如:秒、毫秒,這就存在一個問題,不同機器由于配置不同時間會不相同,而且要想知道時間就必須上機跑代碼很麻煩,所以我們把演算法花費的時間與其中陳述句執行次數成正比,演算法中的基本操作執行個數,為演算法的時間復雜度,

>>> def fun(n:int)->None:
	count = 0;
	for i in range(n):
		for j in range(n):
			count += 1
	for i in range(n*2):
		count += 1
	m = 10
	while m:
		count += 1
		m -= 1
	print(count)

	
>>> fun(10)
130
>>> fun(100)
10210
>>> 

由這個函式中的全域變數count,我們很容易知道執行了F(N)=N^2+2*N+10
其實我們在計算精確的執行次數,而只需要大概執行次數,這里我們使用大O的漸進表示法,

大O的漸進表示法

大O符號(Big O notation):用于描述函式漸進行為的數學符號,常見復雜度如下:

復雜度標記符號描述
常量(Constant) O(1) 操作的數量為常數與輸入的資料的規模無關,
對數(Logarithmic) O(log2 n) 操作的數量與資料的規模 n 的比例是 log2 (n),
線性(Linear) O(n)操作的數量與資料的規模 n 成正比,
平方(Quadratic) O(n2)操作的數量與資料的規模 n 的比例為二次方,
立方(Cubic) O(n3)操作的數量與資料的規模 n 的比例為三次方,
指數(Exponential) O(2n) O(n!)指數級的操作,快速的增長,

大O復雜度運算元曲線:

常見的時間復雜度舉例

例一:常量級

C++:

#include<iostream>
using namespace std;

int func(int n){
	int i = 0;
	for(int j=0;j<1000;i++,j++);
	return i;
}

int main(void){
	cout << func(100) << endl;
	cout << func(1000) << endl;
	
	return 0;
}

python:

>>> def fun(n:int)->int:
	i = 0
	for j in range(1000):
		i += 1
	return i

>>> fun(100)
1000
>>> fun(1000)
1000

時間復雜度為O(1000)但是如果為常數的話,時間復雜度可以直接寫為O(1)

例二:階乘之和 1!+2!+3!+...+n!

C++:

#include<iostream>
using namespace std;

long long func(int n){
	long long sum = 1;
	for(int i=n;i>1;i--){
		sum *= i;
		sum += 1;
	}
	return sum;
}

int main(void){
	cout << func(10) << endl;
	cout << func(20) << endl;
	
	return 0;
}

python:

def factSum(n):
    Sum = 1
    for i in range(n,1,-1):
        Sum *= i
        Sum += 1
    return Sum

def factSum2(n):
	Sum = 0
	from math import factorial as fact
	for i in range(1,n+1):
		Sum+=fact(i)
	return Sum

時間復雜度為O(N)

例三:冒泡排序

C++:

#include<iostream>
#include<cmath>
using namespace std; 

int main(void) { 
	double a[100];
	int N; 
	int i = 0, j = 0;
	cin >> N;

	for (i = 0; i<N; i++)
		cin >> a[i];

	for (i = 0; i<N - 1; i++) { 
		for (j = 0; j<N - 1 - i; j++)
		{
			if (a[j]>a[j + 1]) {
				int tmp;
				tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}

	for (i = 0; i<N; i++){
		cout << a[i] << " "; 
	}
	cout << endl;
	
	return 0;
}

python:

>>> def Bubble(a:list)->None:
	for i in range(len(a)-1):
		for j in range(len(a)-1-i):
			if a[j]<a[j+1]:
				a[j],a[j+1]=a[j+1],a[j]

				
>>> b = [*range(1,6)]
>>> Bubble(b)
>>> b
[5, 4, 3, 2, 1]
>>> 

由這個程式我們可以知道F(N)=N-1+N-2+.....+1=(N^2-N)/2

例四:二分查找

C++:

template<class T>  

int Binary_Search(T *x, int N, T keyword)  
{  
    int low = 0, high = N-1,mid;  
    while(low <= high)  
    {  
        mid = (low + high)/2;  
        if(x[mid] == keyword)  
            return mid;  
        if(x[mid] < keyword)  
            low = mid + 1;  
         else  
            high = mid -1;  

    }  
    return -1;  
}  

int Binary_Search2(int *a, int low, int high, int key)  
{  
     if(low > hign) 
          return -1;
     int mid = (low + high) / 2;  
     if(a[mid] == key)  
          return mid;  
     if(a[mid] > key)  
          return Binary_Search2(a, low, mid-1, key); 
     else  
          return Binary_Search2(a, mid+1, high, key);
}  

python:

def binSearch(a:list,x:int):
	left,right = 0,len(a)
	while left<=right:
		mid = (left+right)//2
		if x>a[mid]:
			left = mid+1
		elif x<a[mid]:
			right = mid-1
		elif x==a[mid]:
			return mid
	return -1

這個演算法對于每次查找,找不到就會將范圍縮小二倍,時間復雜度也就是O(log2 N)

例五:遞回階乘

C++:

#include<iostream>
using namespace std;

long long func(int n){
	long long sum = 1;
	for(int i=n;i>0;i--)
		sum *= i;

	return sum;
}

int main(void){
	cout << func(10) << endl;
	cout << func(20) << endl;
	
	return 0;
}

python:

>>> def F(n:int)->int:
	if n: return F(n-1)*n
	return 1

>>> F(0)
1
>>> F(1)
1
>>> F(2)
2
>>> F(3)
6
>>> F(4)
24
>>> F(5)
120
>>> 

這個也非常簡單return的 值為F(N-1)*F(N-2)........F(0)一共是N+1個,所以中間的代碼也就執行了N+1次,所以時間復雜度是O(N)

例六:斐波那契數列

C++:

#include<iostream>
using namespace std;

long long func(int n){
	if (n<3) return 1;
	return func(n-1)+func(n-2);
}

int main(void){
	cout << func(10) << endl;
	cout << func(20) << endl;
	
	return 0;
}

python:

def F(n:int)->int:
    if n<3: return 1
    return F(n-1)+F(n-2)

這個函式的時間復雜度很多文章都說它是指數級O(2?)的,但我還是用python全域變數法來測驗:

>>> def F1(n):
	global count
	count += 1
	if n<3: return 1
	return F1(n-1)+F1(n-2)
 
>>> count=0; F1(20),count
(6765, 13529)
>>> count=0; F1(25),count
(75025, 150049)
>>> count=0; F1(30),count
(832040, 1664079)
>>> 6765*2,75025*2,832040*2
(13530, 150050, 1664080)
>>> 6765*2-1,75025*2-1,832040*2-1
(13529, 150049, 1664079)
>>> 

看全域變數n終值的測驗結果,可知: F(n)的運算次數 = 2*F(n) - 1,其中F(n)的通項公式(也就是比內公式)為:

F(n) = \frac{1}{\sqrt{5}}[ (\frac{1+\sqrt{5}}{2})^{n} - (\frac{1-\sqrt{5}}{2})^{n} ]

再來使用cProfile庫函式Profile()來驗證一下:

>>> from cProfile import Profile
>>> def F(n:int)->int:
    if n<3: return 1
    return F(n-1)+F(n-2)

>>> cp = Profile()
>>> cp.enable(); n = F(30); cp.disable()

>>> cp.print_stats()
         1664081 function calls (3 primitive calls) in 0.708 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1664079/1    0.708    0.000    0.708    0.708 <pyshell#7>:1(F)
        1    0.000    0.000    0.000    0.000 rpc.py:614(displayhook)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


>>> 2*F(30)-1
1664079
>>> 2**30
1073741824
>>> 30**3
27000
>>> 

結果cp.print_stats()輸出的function calls的值正好也等于2*F(30)-1,所以我認為這個函式的時間復雜度應在立方級O(n3)和指數級O(2?)之間,比O(n3)大好幾十倍但要比O(2?)小兩到三個數量級,實際上應該是這樣一個“數量級”:

空間復雜度

空間復雜度也是一個數學運算式,是對一個演算法在運行程序中臨時占用儲存空間大小的度量,

空間復雜度不是程式占用了多少bytes空間,因為這個也沒太大意義,所以空間復雜度算的是變數的個數,也用O()來表示,

注意:函式運行所需要的堆疊空間在編譯的時候就已經確認好了,因此空間復雜度主要通過函式在運行時顯示申請的空間來確定,

例一:冒泡排序

源代碼略(參見上一節內容空間復雜度,以下同)實際上這里開辟的空間只有i,j 所以空間復雜度為O(1),

例二:階乘函式

答案是O(N)
這里如果對函式堆疊幀不是很了解的話,這個原理也就不會知道,函式堆疊幀可以簡單的理解為:函式每被呼叫一次,都會在堆疊區上開辟一塊空間,所以F(N)會呼叫F(N-1),F(N-1)會呼叫F(N-2)…同時會呼叫N個F,每個F都會開辟一塊空間,所以空間復雜度為:O(N),

示例三:斐波那契數列

通過上面的實體我們就可以知道斐波那契數列實際上開辟了N個Fib函式空間,只是這些函式空間會被重復利用,總共被運行2^n-1次,所以空間復雜度為:O(N),

(本篇完)

學習交流 Python 的群二維碼:

http://qr01.cn/FHYKEa

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/330886.html

標籤:其他

上一篇:使用opencv將影像的輪廓提取為連續路徑

下一篇:perl的帶符號的日期時間減法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more