幾個時間復雜度O(logN)的算法


1 二分查找算法

int BinarySearch(const ElementType A[], ElementType X, int N)
{
    int mid, right, left;
    right = 0;
    left = N - 1;

    while(right <= left){           // 不斷更新左右邊界的索引(實質是每次循環將待查元素減半,實現了O(logN)時間復雜度),直到左索引大於右索引
        mid = (right + left)/2;
        if(A[mid] > X)
            left = mid - 1;
        else if(A[mid] < X)
            right = mid + 1;
        else
            return mid;
    }
    return -1;
}

二分查找算法適合:只需查找,不需要插入(O(N)復雜度?)和刪除的情況。如查詢元素周期表這種較穩定的數據。

2 歐幾里德算法(求最大公因數)

 1 unsigned int Gcd(unsigned int M, unsigned int N)
 2 {
 3     unsigned int Rem;
 4      
 5     while(N > 0){
 6         Rem = M % N;
 7         M = N;
 8         N = Rem;
 9     }           
10     
11     return M;
12 }

若M > N,則第一次循環交換M和N。

若想分析其時間復雜度,則要求循環次數,即生成余數的次數。

可以證明: 當M > N, 則M % N < M / 2

證明:當N <= M/2 時,M % N < M / 2

         當N > M/2時,M - N < M / 2,那么也有M % N < M/2. 

   結論成立。

由此可得:有M、N(M>N)兩個正整數,第一次循環后其余數小於M/2,第二次循環后其余數小於N/2,所以可以說,每兩次循環后,余數最多為原值的一半。

所以最大的求余次數為2logN = O(logN).

2logN並不精確,即使在最壞的情況(如M、N是相鄰的fibonacci數)下,仍然可被優化為1.44logN(每次M和余數的商為1.618,則求余的次數為N關於1.618的對數,即1.44logN)。歐幾里德算法的平均性能需要大量篇幅的精確計算,迭代次數的平均值約為(12ln2 lnN) / π^2+1.47。

3 求冪

最簡單的遞推公式:Xn = Xn-1 * X;

         X0 = 1;

需要Θ(n)步和Θ(n)空間

還可以: Xn = X n/2 * X n/2 = (X * X)n/2    X is even

     X= X(n-1)/2 * X(n-1)/2  * X = (X * X)(n-1)/2 * X     X is odd

long int Pow(long int X,unsigned int N)
{
    if(N == 0)
        return 1;if(N % 2 == 1)
        return Pow(X * X, N/2);
    else
        return Pow(X * X, (N-1)/2) * X;
}

需要Θ(logN)步和Θ(logN)空間。

還可以:Xn = X n/2 * X n/2     X is even

    Xn = Xn-1 * X          X is odd

 
1 long int Pow(long int X, unsigned int N)
2 {
3     if(N == 0)
4         return 1;
5     if(N % 2 == 0)
6         return Pow(X*X, N/2);
7     else
8         return Pow(X, N-1) * X;
9 }   

 

需要Θ(logN)步和Θ(logN)空間。

在不影響正確性的前提下對代碼進行微調是很有趣的。

如對第六行的修改:

1) return Pow(Pow(X,2), N/2);

看上去正確,但當N=2時,return Pow(Pow(X,2),1)調用Pow(X,2),Pow(X,2)繼續調用Pow(X,2),每次遞歸沒有向基准情況推進,無限循環直到崩潰。

2) return Pow(Pow(X, N/2), 2);

看上去正確,但當N = 2時, 調用Pow(X,2),再調用Pow(X,2)...,同(1).

3) return Pow(X, N/2) * Pow(X, N/2);

正確,但影響效率。

每次調用都有兩次遞歸。則總步數為20 + 21 + 22 + ... 2logN  = 2logN+1 -1 = 2N-1

則需O(N)步和O(N)空間。(待驗證)

 以及和上述算法相同的迭代算法。

long int Pow(long int X, unsigned N)
{
    long int a = 1;

    while(N > 0){
        if(N % 2 != 0)
            a *= X;
        N = N/2;
        X *= X;
    }
    return a;
}

例如 X23 = X * (X * X)11 = X * X2 * (X2 * X2)5 = X * X2 * X4 *(X4*X4)2 = X * X2 * X4 * (X8 * X8)1 = X * X2 * X4 * X16 = X23

該程序中定義了一個變量a,整個程序中保證a * XN不變,則當N = 0時,a *X= a * X0 = a,說明這是a的值即我們要求的值。

通常,定義一個不變量,使它在狀態間保持不變,這種技術是構建迭代算法的強有力的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM