【數據結構】淺析時間複雜度與空間複雜度

衆所周知,衡量算法效率的標準爲:時間複雜度 和 空間複雜度

通俗地來講,時間複雜度就是一個 程序要被執行的次數,它是一個近似值,而不是執行的時間。空間複雜度,是程序執行過程中所 佔用的最大內存

接下來通過兩段代碼來說明下如何計算一個程序的 時間複雜度 以及 空間複雜度:

/*以 32 位機爲例*/
int SumMemory(int n) // 時間複雜度:共執行 2n + 5 次,用大 O 表示法記爲 O(n);空間複雜度: 4n + 12 字節,用大 O 表示法記爲 O(n)
{
	int i = 0;  // 時間複雜度:執行 1 次;空間複雜度: i 爲 int 型,佔 4 個字節
	int ret = 0; // 時間複雜度:執行 1 次;空間複雜度: 4 個字節
	int * data = (int *)malloc(n * sizeof(int)); // 時間複雜度:執行 1 次;空間複雜度: 4 * n 個字節

	for (i = 0; i < n; i++) // 時間複雜度:循環 n 次,因此執行 n 次;空間複雜度: 0,因爲這部分內容是在 CPU 中運行,不佔用內存
	{
		data[i] = i + 1;
	}

	for (i = 0; i < n; i++) // 時間複雜度:循環 n 次,因此執行 n 次;空間複雜度: 0,因爲這部分內容是在 CPU 中運行,不佔用內存
	{
		ret += data[i];
	}

	free(data); // 時間複雜度:執行 1 次;空間複雜度: 0,因爲這部分內容是在 CPU 中運行,不佔用內存
	data = NULL; // 時間複雜度:執行 1次;空間複雜度: 4 個字節

	return ret;
}

這段代碼一共執行了 2n + 5 次,當 n 趨於無窮大時,5 對於 2n 的影響少之又少,因此可以將其刪掉,本質上來說,複雜度的計算是個近似運算,那麼 n 的係數也可以置爲 1,只需比較 n 的冪次即可。

/*以 32 位機爲例*/
int SumLoop(int n) // 時間複雜度:共執行 n + 2 次,用大 O 表示法記爲 O(n);空間複雜度: 8 個字節,用大 O 表示法記爲 O(1)
{
	int i = 0; // 時間複雜度:執行 1 次;空間複雜度: i 爲 int 型,佔 4 個字節
	int ret = 0; // 時間複雜度:執行 1 次;空間複雜度: 4 個字節

	for (i = 0; i < n; i++) // 時間複雜度:循環 n 次,因此執行 n 次;空間複雜度: 0,因爲這部分內容是在 CPU 中運行,不佔用內存
	{
		ret += i;
	}

	return ret;
}

這段代碼一共執行了 n + 2 次,當 n 趨於無窮大時,2 對於 n 的影響少之又少,因此可以將其刪掉,本質上來說,複雜度的計算是個近似運算,只需比較 n 的冪次即可。

/*以 32 位機爲例*/
int SumFormula(int n) // 時間複雜度:執行 2 次,用大 O 表示法記爲 O(1);空間複雜度: 4 個字節,用大 O 表示法記爲 O(1)
{
	int ret = 0; // 時間複雜度:執行 1 次;空間複雜度: 4 個字節

	if (n > 0)
	{
		ret = (1 + n) * n / 2; // 時間複雜度:加減乘除運算只是一個運算指令,因此執行 1 次;空間複雜度: 0,因爲這部分內容是在 CPU 中運行,不佔用內存
	}
	else
	{
		;
	}

	return ret;
}

這段代碼一共執行了 2 次,本質上來說,複雜度的計算是個近似運算,將 2 看做 1 即可。

總結下推導 O(n) 的方法:

  1. 首先用常數 1 取代所有的加法常數;
  2. 只保留 n 的最高冪次;
  3. 若最高冪次存在且不爲 1,則將這個最高冪次的係數置爲 1 即可。

最後分析下折半查找和菲波那切數列數列的時間複雜度以及空間複雜度:

  • 折半(二分)查找
// 以 32 位機爲例
/*
*	函數名稱:BinarySearch
*
*	函數功能:二分查找排好序的數組中的某個元素
*
*	入口參數:pArr, key, len
*
*	出口參數:mid
*
*	返回類型:int
*/

int BinarySearch(int * pArr, int key, int len) // 空間複雜度:12 個字節,用大 O 表示法記爲O(1)
{
	int left = 0;  // 空間複雜度:4 個字節
	int right = len - 1; // 空間複雜度:4 個字節
	int mid = 0; // 空間複雜度:4 個字節

	assert(NULL != pArr);
	
	while (left <= right) {
		mid = (left & right) + ((left ^ right) >> 1);

		if (pArr[mid] == key) {
			return mid;
		} else if (pArr[mid] > key) {
			right = mid - 1;	
		} else {
			left = mid + 1;
		}
	}

	return -1;
}

時間複雜度的分析:假設有 n 個元素,經過第一次查找後,查找區間縮短爲 n / 2,經過第二次查找後,查找區間縮短爲 n / 4,…… 那麼經過 k 次查找後,查找區間變爲 n / 2^k(ps: n 除上 2 的 k 次方)。不難發現,k 其實就是循環次數,那麼最終找到元素 key 時,n / 2^k = 1(ps: 這裏等於 1 就表示找到了元素 key),因此可以得出最壞情況下該算法的時間複雜度爲 O(log2n)。(ps: log2n表示以 2 爲底,n 的對數)

綜上所述,折半(二分)查找 的時間複雜度爲 O(log2n),空間複雜度爲 O(1)

  • 遞歸實現斐波那契數列
/*
*	函數名稱:Fibonacci
*
*	函數功能:求第n個菲波那切數(遞歸)
*
*	入口參數:n
*
*	出口參數:1 or Fibonacci(n-1) + Fibonacci(n-2)
*
*	返回類型:int
*/

int Fibonacci(int n)
{
	if (n <= 2) {
		return 1;
	} else {
		return Fibonacci(n-1) + Fibonacci(n-2);
	}
}

時間複雜度的分析:菲波那切數可以看做一個樹結構,1 分爲 2,2 分爲 4,4 分爲 8,... 而要去求第 n 個斐波那契數,就需要被分解成 2^n - 1 個數字,也就是需要執行 2^n - 1 次,用大 O 表示法記爲 O(2^n)。

空間複雜度的分析:計算第 n 個菲波那切數,會調用 n - 2 次 Fibonacci() 函數,每次調用都會開闢棧空間,那麼就會佔用 4*(n-2)個字節,用大 O 表示法記爲 O(n)。

綜上所述,遞歸實現菲波那切數列的時間複雜度爲 O(2^n),空間複雜度爲 O(n)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章