目錄
一、計算的資源
程序運行時需要的資源有兩種,即計算時間和存儲時間。一個算法對這兩個資源的使用程度可以用來衡量算法的優劣。
- 時間複雜度:程序運行所需的時間。
- 空間複雜度:程序運行所需要的存儲空間。
可以用clock()函數統計程序運行的時間。
#include <bits/stdc++.h>
using namespace std;
int main(){
clock_t start, end;
start = clock();
...
end = clock();
cout << (double)(end - start) / CLOCK_PER_SEC << endl;
}
由於程序的運行是時間依賴於計算機的性能,不同的計算機結果不同,所以通常用程序執行的 “次數” 來衡量,例如執行了 n 次就記爲O(n)。
下面給出一個例子:
Problem Description:
給你n個整數,請按從大到小的順序輸出其中前m大的數。
Input:
每組測試數據有兩行,第一行有兩個數n,m(0<n,m<1000000),第二行包含n個各不相同,且都處於區間[-500000,500000]的整數。
Output:
對每組測試數據按從大到小的順序輸出前m大的數。
思路是先對100萬個數進行排序,然後輸出前m大的數。
1、冒泡排序
首先用最簡單的冒泡排序解決問題。
//#include <bits/stdc++.h>
#include <stdio.h>
using namespace std;
int a[1000001];
# define swap(a, b){int temp = a; a = b; b = temp;}
int m, n;
void bubble_sort(){
for(int i = 1; i <= n - 1; i++){
for(int j = 1; j <= n - i; j++){
if(a[j] > a[j + 1])
swap(a[j], a[j + 1]);
}
}
}
int main(){
while(~scanf("%d %d", &n, &m)){
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
bubble_sort();
for(int i = n; i >= n - m + 1; i--){
if(i == n - m + 1) printf("%d\n", a[i]);
else printf("%d ", a[i]);
}
}
return 0;
}
下面分析程序的時間和空間效率。
(1)時間複雜度
在bubble_sort()中有兩層循環,循環次數約爲 n^2/2;在swap(a,b)中做了三次操作;總的操作次數爲 3n^3/2,時間複雜度記爲 O(n^2) 。
(2)空間複雜度
程序使用了int a[1000001] 存儲數據,bubble_sort()也沒有使用額外的空間。int 是32位整數,所以a[1000001]共使用了4MB的空間。這是冒泡排序的優點,它不額外佔用時間。
2、快速排序
快速排序是一種基於分治法的優秀的排序算法。這裏直接引用了STL的sort()函數,它是改良版的快速排序,稱爲“內省式排序”。
直接把上面的程序更改爲
//#include <bits/stdc++.h>
#include <iostream>
#include <algorithm>
using namespace std;
int a[1000001];
# define swap(a, b){int temp = a; a = b; b = temp;}
int m, n;
void bubble_sort(){
for(int i = 1; i <= n - 1; i++){
for(int j = 1; j <= n - i; j++){
if(a[j] > a[j + 1])
swap(a[j], a[j + 1]);
}
}
}
int main(){
while(~scanf("%d %d", &n, &m)){
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
sort(a + 1, a + n + 1); // bubble_sort();
for(int i = n; i >= n - m + 1; i--){
if(i == n - m + 1) printf("%d\n", a[i]);
else printf("%d ", a[i]);
}
}
return 0;
}
時間複雜度爲O(nlogn),剛好通過測試。
3、哈希算法
哈希算法是一種以空間換取時間的算法。
本題的哈希思路是:子啊輸入數字 t 的時候,在a[500000 + t] 這個位置記錄a[500000 + t] = 1, 在輸出的時候逐個檢查 a[i],如果a[i] 等於1,表示這個數存在,打印出前m個數。程序如下:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000001;
int a[MAXN];
int main(){
int n, m;
while(~scanf("%d %d"), &n, &m){
memset(a, 0, sizeof(a));
for(int i = 0; i < n; i++){
int t;
scanf("%d", &t);
a[500000 + t] = 1;
}
for(int i = MAXN; m > 0; i++){
if(a[i]){
if(m > 1) printf("%d ", i - 500000);
else printf("%d\n", i - 500000);
m--;
}
}
}
return 0;
}
程序並沒有做顯示的排序,只是在每次輸入時候按哈希插入到相對應的位置,只有一次操做;n個數輸入完畢,就相當於排序好了。總的時間複雜度是O(n)。
二、算法的定義(省略)
三、算法的評估
上面已經反覆提到,衡量算法性能的主要標準是時間複雜度,爲什麼一般不討論空間複雜度呢?在一般情況下,一個程序的空間複雜度是容易分析的,而時間複雜度往往關係到算法的根本邏輯,更能說明一個程序的優劣。
一個程序或算法的時間複雜度有以下可能。
1、O(1)
計算時間是一個常數,和問題的規模 n 無關。例如,哈希算法,用hash函數在常數時間內計算出存儲位置。
2、O(logn)
計算時間是對數,通常以2爲底的對數,每一步計算後,問題的規模減小一倍。例如在一個長度爲 n 的有序數列中查找某個數,用折半查找的方法只要 logn 次就能找到。
O(logn) 和 O(1) 沒有太大差距。
3、O(n)
計算時間隨着規模 n 線性增長。在很多情況下,這是算法可能得到的最優複雜度,因爲對輸入的 n 個數,程序一般需要處理所有的數,即計算 n 次。例如查找一個無序數列中的某個數,可能需要檢查所有的數。
4、O(nlogn)
這常常是算法能得到的最優複雜度。例如分治法,一共O(logn)個步驟,每個步驟對每個數操做一次,所以總的時間複雜度是O(nlogn)。
5、O(n^2)
一個兩重循環算法,時間複雜度是O(n^2)。例如冒泡排序是典型的兩重循環。
6、O(2^n)
一般對應集合問題,例如一個集合中有n個數,要求輸出它的所有子集,子集有2^n個。
7、O(n!)
在集合問題中,如果要求按順序輸出所有的子集,那麼複雜度就是O(n!)。