好好學習《算法競賽入門到進階》 第二章-算法複雜度

目錄

 

一、計算的資源

1、冒泡排序

2、快速排序

3、哈希算法

二、算法的定義(省略)

三、算法的評估


一、計算的資源

程序運行時需要的資源有兩種,即計算時間和存儲時間。一個算法對這兩個資源的使用程度可以用來衡量算法的優劣。

  • 時間複雜度:程序運行所需的時間。
  • 空間複雜度:程序運行所需要的存儲空間。

可以用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)。

下面給出一個例子:

hdu 1425 “sort”

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!)。

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