演算法分析基本概念

ai-exception發表於2018-05-14

二分搜尋

Linearsearch

/*
 * 輸入:n個元素的陣列A[1...n]、x
 * 輸出:如果x=A[j]&&1<=j<=n,則輸出j,否則輸出0
 */

int Linearsearch(int *A,int x, int n){
    int j=0;
    while (j<n&&x!=A[j]){
        j++;
    }

    if(x==A[j])return j;
    return 0;
}

Binarysearch

/*
 * 輸入:n個元素的升序陣列A[1...n]、x
 * 輸出:如果x=A[j]&&1<=j<=n,則輸出j,否則輸出0
 */

int Binarysearch(int *A, int x, int n) {
    int low = 1, high = n, j = 0;
    while (low <= high && j == 0) {
        int mid = (int) ((low + high) / 2);
        if (x == A[mid])j = mid;
        else if (x < A[mid])high = mid - 1;
        else low = mid + 1;
    }

    return j;
}

要注意二分搜尋的輸入一定是一個升序的陣列,實質就是一個二叉搜尋樹(所以也把二分搜尋的執行描述為決策樹),對於一個大小為n的排序陣列,演算法Binarysearch執行比較的最大次數為int(logn)+1(如果輸入陣列不是遞增排好序的,則可在nlogn內對其進行排序後再進行二分搜尋)。

合併兩個已排序的表

/*
 * 輸入:陣列A[1...m]和它的三個索引p,q,r,1<=p<=q<r<=m,p、q、r滿足A[p...q]、A[q+1...r]分別按照升序排列
 * 輸出:合併兩個子陣列A[p...q]和A[q+1...r]的陣列A[p...r]
 */

void Merge(int *A, int p, int q, int r) {
    int B[r + 1];//B[p...r]是輔助陣列
    int s = p, t = q + 1, k = p;//s指向A[p...q]子陣列,t指向A[q+1...r]子陣列,k指向B陣列
    while (s <= q && t <= r) {
        if (A[s] <= A[t]) {
            B[k] = A[s];
            s++;
        } else {
            B[k] = A[t];
            t++;
        }

        k++;
    }

    if (s = q + 1) {//說明s指向的陣列已經遍歷完了
        for (int i = t; i <= r; ++i) {
            B[k++] = A[i];
        }
    } else {
        for (int i = s; i <= r; ++i) {
            B[k++] = A[i];
        }
    }

    for (int j = p; j <= r; ++j) {
        A[j] = B[j];

    }
}

設Merge演算法要合併兩個大小分別為n1和n2的陣列(n1

選擇排序

/*
 * 輸入:n個元素的陣列A[1...n]
 * 輸出:按非降序排列的陣列A[1...n]
 */

void SelectionSort(int *A, int n) {

    for (int i = 0; i < n; ++i) {

        for (int j = i + 1; j <= n; ++j) {
            if (A[i] > A[j]) {
                int t = A[i];
                A[i] = A[i];
                A[i] = t;
            }
        }
    }

}

演算法SelectionSort所需的元素比較次數為n(n-1)/2(n-1+n-2+n-3+…+2+1=n(n-1)/2),因為每次交換需要3次賦值,所以元素的賦值次數介於0到3(n-1)之間。

插入排序

思路

首先將第二個數與第一個數進行對比,如果第二個數比第一個數小,則將第二個數插入到第一個數之前,這樣保證前兩個數是有序的;
接下來將第三個數與前兩個數對比,比較的思路是先將第三個數存下來(記為x),然後將第三個數與第二個數比較,如果第二個數比第三個數大,則直接將第二個數向後移動一位,如果第二個數不比第三個數大,則說明此時前三個數都是有序的,因為之前前兩個數是有序的,比較到最後,將x放到第三個數比較的終止位置即可。以此類推,將後面的i個數分別其前面的i-1個數進行對比,並將其插入到第一個比其大的數前面,最後即可完成排序。

程式碼實現

/*
 * 輸入:n個元素的陣列A[1...n]
 * 輸出:按非降序排列的陣列A[1...n]
 */

void InsertionSort(int *A, int n) {
    for (int i = 2; i <= n; ++i) {
        int x = A[i];
        int j = i - 1;
        while (j > 0 && A[j] > x) {
            A[j + 1] = A[j];
            j--;
        }
        A[j + 1] = x;
    }
}

執行演算法SelectionSort的元素比較次數在n-1到n(n-1)/2之間,元素賦值次數等於元素比較次數加上n-1.

自底向上合併排序

/*
 * 輸入:n個元素的陣列A[1...n]
 * 輸出:按非降序排列的陣列A[1...n]
 */


void Merge(int *A, int p, int q, int r);

void BottomUpSort(int *A, int n) {
    int t = 1;
    while (t < n) {
        int s = t, t = 2 * s, i = 0;
        while (i + t <= n) {
            Merge(A, i + 1, i + s, i + t);
            i = i + t;
        }
        if (i + s < n) {
            Merge(A, i + 1, i + s, n);
        }
    }
}

用演算法BottomUpSort對n個元素的陣列進行排序,當n為2的冪時,元素比較次數在(nlogn)/2到nlogn-n+1之間。執行該演算法的元素賦值次數為2nlogn。

時間複雜性

O

前文提到演算法InsertionSort執行的運算次數至多為cn^2,其中c為某個適當選擇的正常數。這時我們說演算法InsertionSort的執行時間是O(n^2),說明當排序元素的個數等於或超過某個閾值n0時,對於某個常亮c,執行時間是cn^2,O符號描述的是一個上界但不一定是演算法的實際執行時間,比如當排序一個已經排序好的陣列時InsertionSort的執行時間就不是O(n^2)而是O(n)了。

Ω

相比於O,Ω描述的是演算法執行的下界,比如演算法InsertionSort的運算時間至少是cn,則稱演算法InsertionSort的執行時間是Ω(n),即無論何時,當被排序的元素個數等於或超過某一個閾值n0時,對於某個常數c,演算法的執行時間至少是cn。

Senta

Senta描述的是一個確切界限,如果對於任意大小等於或超過某一閾值n0的輸入,如果執行時間在c1g(n)和c2g(n)之間,則稱演算法的執行時間是Senta(g(n))。

複雜性類與 o 符號

o符號

O 符號給出的上界可以是“緊”的,也可以是非“緊”的。
2* n^ 2 =O ( n^ 2 ) 是漸近性緊邊界
2 *n = O ( n^ 2 ) 不是漸近性緊邊界

o 符號就用來表示不是漸近性緊邊界的上界
舉例: 2 n = o ( n ) , 2 ^n != o ( n )

直觀上來說,在小 o 符號中, f ( n ) =o ( g ( n )) ,當 n 趨向於無窮大時, f (n ) 函式相當於 g (n ) 就變得不再重要了
即lim->+oof(n)/g(n)=0

w符號

用類比法來講,小 w符號相對於大 Ω符號的關係正如 o 符號相對於 O 符號的關係。
我們用小 w 符號來表示一個漸近性非緊密的下界
比如:

(n^2)/2=w(n )
(n^2)/2!=w(n^2)

lim->+oof(n)/g(n)=oo

空間複雜性

我們把演算法使用的空間 定義 、為:為了求解問題的例項而執行的計算步驟所需要的記憶體空間,它不包括分配用來儲存輸入的空間(為了區分那些在整個計算過程中佔用了少於輸入空間的演算法)

演算法的空間複雜性不可能超過執行時間的複雜性,因為每寫入一個記憶體單元都至少需要一定的時間。所以,如果用 T (n ) 和 S (n ) 分別代表演算法的時間複雜性和空
間複雜性,有: S ( n ) = O ( T ( n )) 。

最優演算法

如果可以證明任何一個求解問題 T的演算法必定是Ω ( f ( n )) ,那麼我們把在 O ( f ( n )) 時間內求解問題T的任何演算法都稱為問題 T的最優演算法。

如何估計演算法的執行時間

  • 計算迭代次數
  • 計算基本運算的頻度

一般來說,在分析一個演算法執行時間時,可以找出這樣一個元運算,它的頻率至少和任何其他運算的頻度一樣大,稱這樣的運算為基本運算。我們還可以放寬這個定義,把那些頻度和執行時間成正比的運算包括進來。

  • 使用遞推關係

如果一個演算法本身是遞迴演算法,那麼計算這個演算法執行時間的函式通常也是遞迴的,即是指,這個函式的定義中引用了函式自身。即便一個演算法本身是非遞迴的,我們有時也可以用遞迴式來計算它的執行時間。

lim->+oof(n)/g(n)!=oo f(n)=O(g(n))
lim->+oof(n)/g(n)!=0 f(n)=Ω(g(n))
lim->+oof(n)/g(n)==c f(n)=Senta(g(n))

相關文章