「データ構造とアルゴリズム分析」研究ノート-第2章-アルゴリズム分析

アルゴリズム分析

問題を解くためのアルゴリズムが特定の証明法によって決定され、正しいことが証明された場合、アルゴリズムの実行時間と、実行時間によって占められるスペースを次に判断する必要があります。この章では主に

  • プログラムの実行時間を推定する
  • プログラムの実行時間を短縮する
  • 再帰的リスク
  • 数を乗じてその力を取得し、2つの数の最大公約数を計算するための効果的なアルゴリズム

2.1数学的基礎

  1. N> = n0、T(N)<= cf(N)のような通常の数cとn0がある場合、それはT(N)= 0(f(N))として記録されます。ここでは、T(N)について話しています。成長トレンドはf(N)の成長トレンドを超えません。ここでは、時間の複雑さに関する定義をよく使用します。f(N)は、T(N)の上限とも呼ばれます。
  2. N> = n0、T(N)> = cg(N)のような通常の数cとn0がある場合、それはT(N)=Ω(f(N))として記録されます。ここでは、T(N)について話しています。の成長トレンドはg(N)の成長トレンド以上です。ここで、g(N)はT(N)の下限であると言われています。
  3. T(N)=Θ(h(N))T(N)= O(h(N))、およびT(N)=Ω(h(N))の場合に限ります。ここでは、T(N)とg(N)の成長傾向は同じであると言われています。
  4. T(N)= O(p(N))、およびT(N)!=Θ(p(N))の場合、T(N)= o(p(N))。ここでは、T(N)の成長傾向は常にp(N)よりも小さいと言われています。そして平等はありません

上記のステートメントはあいまいです。簡単な例を挙げてください。g(N)= N ^ 2の場合、g(N)= O(N ^ 3)およびg(N)= O(N ^ 4)で問題ありません。g(N)=Ω(N)、g(N)=Ω(1)も正しい。g(N)=Θ(N ^ 2)は、g(N)= O(N ^ 2)、g(N)=Ω(N ^ 2)を意味します。つまり、現在の結果はg(N)自体の成長傾向と最も一致しています。示されているように:

覚えておくべき重要なルールが3つあります。

  1. T1(N)= O(f(N))、およびT2(N)= O(g(N))の場合、
    • T1(N)+ T2(N)= max(O(f(N))、O(g(N)))、
    • T1(N)* T2(N)= 0(f(N)* g(N))
  2. T(N)が次数kの多項式である場合、T(N)=Θ(N ^ k)
  3. 任意の定数kの場合、log ^ k N = O(N)。対数の増加が非常に遅いことを示しています

大きなO表記を使用する場合は、高次の累乗を維持し、定数項と低次の累乗を破棄します。図に示すように、関数は成長率によって分類されます。

極限lim f(N)/ g(N)(n->∞)を計算することにより、2つの関数f(N)とg(N)の相対成長率を常に決定できます。ロビタ基準を使用して計算できます。

  • 制限は0であり、f(N)= o(g(N))
  • 制限はcおよびc!= 0であり、f(N)=Θ(g(N))
  • 制限は∞であり、g(N)= o(f(N))
  • 限界スイング:2つは何もする必要はありません

たとえば、f(N)= NlogNおよびg(N)= N ^ 1.5の相対的な成長率は、f(N)/ g(N)= logN / N ^ 0.5 = log ^ 2 N / Nとして計算できます。そして、Nは、logNのどのパワーよりも速く成長します。したがって、g(N)の成長はf(N)の成長よりも速い

ルピダ基準:lim f(N)=∞(n->∞)およびlim g(N)=∞(n->∞)の場合、lim f(N)/ g(N)= lim f '( N)/ g '(N)(n->∞)。

2.2モデル

問題分析を容易にするために、モデルコンピュータを想定しています。基本的な命令を実行し、時間の単位を消費し、メモリが無制限であると想定します。

2.3分析すべき問題

  1. 小さな入力の場合、スマートアルゴリズムを設計するために多くの時間を費やす価値はありません。
  2. データの読み取りはボトルネックであり、データが読み込まれると、適切なアルゴリズムの問​​題が迅速に解決されます。したがって、問題のボトルネックにならないように、アルゴリズムを十分に効果的にすることが重要です。

2.4実行時間の計算

2.4.1例

  • 2つのアルゴリズムにほぼ同じ時間がかかる場合、どちらのプログラムの方が速いかを判断する最善の方法は、それらをコーディングして実行することです。
  • 分析を簡略化するために、実行時間を計算するためにBig O表記を使用します。BigOは上限です。したがって、分析結果は、最悪の場合、指定された時間内にプログラムが完了できることを保証するためのものです。手続きは早く終了するかもしれませんが、遅れることはありません
// 书上例程
// 计算i^3的累加求和
int sum (int N)
{
    int i, PartialSum;
    PartialSum = 0;             /*1*/
    for(i = 1; i <= N; i++)     /*2*/
        PartialSum += i * i * i;/*3*/
    return PartialSum;          /*4*/
}

ここでは、分析の各行について:

  1. 1つの時間単位を使用:1つの割り当て
  2. 支出1 + N + 1 + N = 2N + 2時間単位:1割り当て、N + 1判断、N追加
  3. コストN(2 + 1 + 1)= 4N時間単位:2乗算、1加算、1代入、N回実行
  4. 1時間単位:1回の返品

合計コストは、1 + 2N + 2 + 4N + 1 = 6N + 4時間単位です。

しかし、実際には、この分析を毎回行う必要はありません。何百または何千行ものプログラムに直面すると、この分析をすべての行に対して行うことができないためです。最高次数のみを計算する必要があります。forループが最も時間がかかることがわかります。したがって、時間の複雑さはO(N)です。

2.4.2一般的な規則

  1. forループ:forループの実行時間は、多くてもforループ内のステートメントの実行時間に反復回数を掛けたものでなければなりません
  2. forループを入れ子になった:インサイドアウト分析サイクル。ネストされたループのグループ内のステートメントの合計実行時間は、ステートメントの実行時間にすべてのforループのサイズを掛けた積です。
for (i = 0; i < N; i++)
    for (j=0; j < N; j++)
        k++;    // 1 * N * N = N^2,时间复杂度为O(N^2)
  1. 順次ステートメント:各ステートメントの実行時間を合計できます。最大値を取ります。
for (i = 0; i < N; i++)
    A[i] = 0;   // O(N)
for (i = 0; i < N; i++)
    for (j = 0; j < N; j++)
        A[i] += A[j] + i + j;   // O(N^2)
// 总时间为O(N) + O(N^2),因此取最高阶,总时间复杂度为O(N^2)
  1. if-elseステートメント:2つのブランチでの判定時間とより長い実行時間

再帰呼び出しで繰り返し作業を行うことは避けたいです。

2.4.3最大のサブシーケンスと問題の解決策

最大サブシーケンス問題:整数A1、A2、...、AN(負の数を持つ可能性があります)が与えられた場合、連続する整数の合計の最大値を求めます。すべての整数が負の場合、サブシーケンスの最大合計は0です

  1. オプション1、時間の複雑さO(N ^ 3)
// 书上例程
int
MaxSubsequenceSum(const int A[], int N)
{
    int ThisSum, MaxSum, i, j, k;
    
    MaxSum = 0;
    for (i = 0; i < N; i++) {
        for (j = i; j < N; j++) {
            ThisSum = 0;
            for (k = i; k <= j; k++) {
                ThisSum += A[k];
            }
            
            if (ThisSum > MaxSum) {
                MaxSum = ThisSum;
            }
        }
    }
    
    return MaxSum;
}
  1. ソリューション2、時間の複雑さO(N ^ 2)。スキーム1と比較して、最も内側のループは破棄されます
int
MaxSubsequenceSum(const int A[], int N)
{
    int ThisSum, MaxSum, i, j, k;
    
    MaxSum = 0;
    for (i = 0; i < N; i++) {
        ThisSum = 0;
        for (j = i; j < N; j++) {
            ThisSum += A[k];
            if (ThisSum > MaxSum) {
                MaxSum = ThisSum;
            }
        }
    }
    
    return MaxSum;
}
  1. スキーム3、時間の複雑さO(NlogN)。分割統治戦略を使用します。「フェン」は、データを2つの部分に分割することです。つまり、問題を2つのほぼ等しいサブ問題に分割し、それらを再帰的に解決します。「ガバナンス」は、2つの部分の最大サブシーケンスの合計を計算し、結果をマージすることです。この問題では、最大サブシーケンスは、左半分、右半分、左半分と右半分(左半分の最後の要素と右半分の最初の要素を含む)の3つの場合に発生する可能性があります。3番目のケースの最大サブシーケンス合計は、左半分の最後の要素を含む最大サブシーケンス合計と、右半分の最初の要素を含む最大サブシーケンス合計の合計です。
// 书上例程
int 
max3(int a, int b, int c)
{
    int x;
    x = a > b? a: b;
    return (x > c? x: c);    
}

int
MaxSubsequenceSum(const int A[], int Left, int Right)
{
    int MaxLeftSum, MaxRightSum;
    int MaxLeftBorderSum, MaxRightBorderSum;
    int MaxLeftThisSum, MaxRightThisSum;
    int Center;
    int cnt;
    
    if (Left == Right) {
        if (A[Left] > 0) {
            return A[Left];
        } else {
            return 0;
        }
    }
    
    Center = (Left + Right) / 2;
    MaxLeftSum = MaxSubsequenceSum(A, Left, Center);
    MaxRightSum = MaxSubsequenceSum(A, Center + 1, Right);
    
    MaxLeftBorderSum = 0;
    MaxLeftThisSum = 0;
    for (cnt = Center; cnt >= Left; cnt--) {
        MaxLeftThisSum += A[cnt];
        if (MaxLeftThisSum > MaxLeftBorderSum) {
            MaxLeftBorderSum = MaxLeftThisSum;
        }
    }
    
    MaxRightBorderSum = 0;
    MaxRightThisSum = 0;
    for (cnt = Center + 1; cnt <= Right; cnt++) {
        MaxRightThisSum += A[cnt];
        if (MaxRightThisSum > MaxRightBorderSum) {
            MaxRightBorderSum = MaxRightThisSum;
        }
    }
    
    return max3(MaxLeftSum, MaxRightSum, MaxRightBorderSum + MaxLeftBorderSum);
}
  1. ソリューション4、時間の複雑さはO(N)です。データは1回だけスキャンされ、一度読み込まれて処理されると、記憶する必要はありません。アレイがディスクに保存されている場合、メインメモリにアレイの一部を保存する必要なく、順次読み取ることができます。そして、いつでも、アルゴリズムは、読み取ったデータのサブシーケンス問題に正しい答えを与えることができますこの特性を持つアルゴリズムは、オンラインアルゴリズム(オンラインアルゴリズム)とも呼ばれます。一定のスペースのみを必要とし、線形時間で実行されるオンラインアルゴリズムは、ほぼ完璧なアルゴリズムです。
//书上例程
int
MaxSubsequenceSum(const int A[], int N)
{
    int ThisSum, MaxSum, j;
    
    ThisSum = MaxSum = 0;
    for (j = 0; j < N; j++) {
        ThisSum += A[j];
        if (ThisSum > MaxSum) {
            MaxSum = ThisSum;
        } else if (ThisSum < 0) {
            ThisSum = 0;
        }
    }
    return MaxSum;
}

2.4.4実行時の対数

アルゴリズムが一定の時間(O(1))を使用して問題のサイズを部分(通常は1/2)に減らす場合、アルゴリズムはO(logN)になります。一方、定数時間を使用して問題を定数だけ減らす場合(問題を1減らすなど)、このアルゴリズムはO(N)になります。

  1. バイナリ検索:バイナリ検索は、時間の複雑さがO(logN)の検索操作を提供します。その前提は、データがソートされていることであり、要素が挿入されるときはいつでも、挿入操作の時間の複雑さはO(N)です。二分探索は、要素が比較的固定されている場合に適しています。
// 书上例程,时间复杂度为O(logN)
#define NotFound -1

int BinarySearch(const ElementType A[], ElementType X, int N)
{
    int low, high, mid;
    low = 0;
    high = N - 1;
    mid = (low + high) / 2;
    
    while (low <= high) {
        if (A[mid] < X) {
            low = mid + 1;
        } else if (A[mid] > X) {
            high = mid - 1;
        } else {
            return mid;
        }
    }
    return NotFound;
}
  1. ユークリッドアルゴリズム:ユークリッドアルゴリズムの名前は非常に高く聞こえます。実際、これはトルネード分割と呼ばれています。2つの整数の最大の共通因子を見つけるときは、一方の整数を使用して他方を除算し、残りを取得します。次に、前の除数を除算して剰余を除算し、新しい剰余を取得します。新しい剰余が0の場合、現在の整数の除数が最大の共通因子です。2回の反復後、残りは最大で元の値の半分になります。反復の最大数は2logN = 0(logN)です
// 书上例程:辗转相除法,时间复杂度O(logN)
int test(unsigned int M, ungisned int N)
{
    unsigned int Rem;
    
    while (N > 0) {
        Rem = M % N;
        M = N;
        N = Rem;
    }
    return M;
}
  • 定理2.1:M> Nの場合、M mod N <M / 2。
    証明:N <= M / 2の場合、残りはN未満でなければならないため、M mod N <M / 2; N> M / 2の場合、M-N <M / 2、つまりM mod N <M / 2 定理が証明される
  1. べき乗演算:整数のべき乗を求めます。それがX ^ Nです。必要な乗算の数は最大2logNであるため、問題を半分に分割するには、最大2つの乗算が必要です(Nは奇数です)。
// 书上例程,时间复杂度O(logN)
long int Pow(long int X, unsigned int N)
{
    if (N == 0) {
        return 1;
    } else if (N == 1) {
        return X;
    }
    
    if (isEven(N)) {
        return Pow(X * X, N / 2);
    } else {
        return Pow(X * X, N / 2) * X;
    }
}

2.4.5分析をテストする

  1. 方法1:実際のプログラミング、実行時間の結果が分析で予測された実行時間と一致するかどうかを確認します。Nが2倍になると、線形プログラムの実行時間は係数2で乗算され、2次プログラムの実行時間は係数4で乗算され、3番目のプログラムの実行時間は係数8で乗算されます。対数時間で実行されるプログラムの場合、Nが増加すると時々、実行時間は定数だけ増加します。O(NlogN)で実行されるプログラムは、元の実行時間の2倍を少し上回ります。(NX、2N(X + 1))低次項の係数が比較的大きく、Nが十分に大きくない場合、実行時間を明確に観察することは困難です。O(N)とO(NlogN)を練習だけで区別するのは難しい
  2. 方法2:Nの範囲(通常は2の倍数で区切られます)の比率T(N)/ f(N)を計算します。ここで、T(N)は観測された実行時間であり、f(N)は理論的な導関数です実行時間です。計算値が通常の数値に収束する場合は、f(N)が実行時間の理想的な近似であることを意味し、0に収束する場合は、f(N)が大きすぎることを意味します。代表f(N)は小さすぎると推定されます。
//书上例程,时间复杂度O(N^2)
void test(int N)
{
    int Rel = 0, Tot = 0;
    int i, j;
    
    for( i = 1; i <= N; i++) {
        for ( j = i + 1, j <= N; j++) {
            Tot++;
            
            if (Gcd(i,j) == 1) {
                Rel++;
            }
        }
    }
    
    printf("%f", (double)Rel / Tot);
}

2.4.6分析結果の精度

場合によっては、分析で見積もりが大きすぎることがあります。次に、分析をより詳細にする必要があるか、平均実行時間が最悪の場合の実行時間よりも大幅に短く、結果の境界を改善する方法がありません。多くのアルゴリズムでは、最悪の範囲はいくつかの不適切な入力によって達成されますが、実際には通常は過大評価されています。これらの問題のほとんどについて、平均的な状況の分析は非常に複雑であるか、未解決です。最悪の場合の世界は少し悲観的ですが、最もよく知られている分析結果です。

  • 単純なプログラムには単純な分析がない場合がある
  • 下限分析は、特定のアルゴリズムだけでなく、特定の種類のアルゴリズムにも適用できます
  • Gcdアルゴリズムとべき乗アルゴリズムは、暗号化で広く使用されています

アドバイス:

この記事はオリジナルであり、転載を学ぶことを歓迎します_

転載は目立つ位置に記入してください:

ブロガーID:CrazyCatJack

元のブログリンクアドレス:https : //www.cnblogs.com/CrazyCatJack/p/12688582.html


これで第2章と第3章が終わり、特定のデータ構造とアルゴリズムの実装について説明します。気分が良ければ、クリックしてお勧めします。後で一緒に学ぶことができます。応援ありがとうございました!

クレイジーキャットジャック

おすすめ

転載: www.cnblogs.com/CrazyCatJack/p/12688582.html
おすすめ