web-dev-qa-db-ja.com

ネストされたループは線形時間の複雑さを持つことができます

従来のクイックソートアルゴリズムを実行していました。いくつかの場所でパーティションアルゴリズムを調べましたが、実装の違いは非常に微妙でした。ここに2つのアプローチがあります:アプローチ1:ピボットは最後の要素です

partition (arr[], low, high)
{
    // pivot (Element to be placed at right position)
    pivot = arr[high];  

    i = (low - 1)  // Index of smaller element

    for (j = low; j <= high- 1; j++)
    {
        // If current element is smaller than or
        // equal to pivot
        if (arr[j] <= pivot)
        {
            i++;    // increment index of smaller element
            swap arr[i] and arr[j]
        }
    }
    swap arr[i + 1] and arr[high])
    return (i + 1)
}

アプローチ2:ピボットは最初の要素です

Partition(a[], l, h)
{
    pivot = a[l];
    i - l; j = ;h
    while(i  < j)
    {
        while(a[i] <= pivot)
            i++;
        while(a[i] > pivot)
            j--;
        if(i < j)
            swap(a[i], a[j]);
    }
    swap(a[l], a[j]);
    return j;
}

アプローチ1ではforループは1回しか使用されないため、線形時間と呼ばれますが、アプローチ2ではネストされたループがあります。しかし、私の疑いによると、これらのネストされたループによって実行されるタスクは、線形時間以上のものは発生しません。

これに光を当ててください。前もって感謝します。

2
arnie927

ネストされたループは線形時間の複雑さを持つことができますか?

はい。この関数を考えてみましょう:

def linear_time(n):
    for i in range(sqrt(n)):
        for j in range(sqrt(n)):
            something_that_takes_constant_time()

時間の複雑さはO(sqrt(n) * sqrt(n))= O(n)です。

覚えておいてください:

def foo(x):
    part_a(x)
    part_b(x)
    # if part_a(x) has a time complexity of O(f(x))
    # and if part_b(x) has a time complexity of O(g(x))
    # then foo(x) has a time complexity of O(f(x) + g(x))

def foo(x):
    loop f(x) times:
        part_of_loop(x)
    # if part_of_loop(x) has a time complexity of O(g(x))
    # then foo(x) has a time complexity of O(f(x) * g(x))

このアルゴリズムを調べてみましょう:

Partition(a[], l, h)
{
    pivot = a[l]; # O(1)
    i = l; j = h; # O(1)
    while(i < j) # runs ??? times
    {
        while(a[i] <= pivot) # runs O(h - l) times
            i++; # O(1)
        while(a[j] > pivot) # runs O(h - l) times
            j--; # O(1)
        if(i < j) # O(1)
            swap(a[i], a[j]); # O(1)
    }
    swap(a[l], a[j]); # O(1)
    return j; # O(1)
}

したがって、ループの内部の時間の複雑さはO((h-l)* 1 +(h-l)* 1 + 1 * 1)= O(h-l)です。しかし、ループ全体の時間の複雑さは何ですか? (i <j)がtrueである場合と同じ頻度で実行されます。

while(a[i] <= pivot)の後のポスト条件はa[i] > pivotであり、while(a[j] > pivot)の後はa[j] <= pivotになります。 i < jの場合(私たちが気にする唯一の場合、そうでなければループはとにかく終了します!)、2つのインデックスの値が交換されます。つまり、a[i] <= pivota[j] > pivotがもう一度trueになります。つまり、ピボットの反対側にある左端と右端のインデックスを見つけて、すべてが正しく分割されるまで入れ替えます。では、そのループはどのくらいの頻度で実行されますか?本当に言うことはできません、それはピボットの「間違った」側にある要素の数に依存します。最初のループijの後のすべてのループは、少なくとも1ステップ互いに近づくため、上限は(h-l)/ 2 +になります。 1は複雑度O(h-l)です。

今、私は何かをスキップしました:外​​側のループが実行される回数は、内側のループが実行される回数に依存しています。内側のループの実行回数はO(h-l)に向かう傾向があるため、外側のループの実行回数は1に向かう傾向があり、その逆も同様です。彼らは一種のお互いの実行を食べています。前の段落で述べた上限は、内部ループが毎回1回だけ実行され、その複雑さがO(1)になるときに得られるものです。そして、内側のループがそれぞれaおよびb回実行されるたびに、外側のループがO((h - l) / (a + b)で実行される回数が判明しました。言い換えると、外側のループの内容の複雑度はO(a + b)であり、外側のループの実行回数はO((h - l) / (a + b)であり、ループ全体の時間の複雑度はO((h - l) / (a + b) * (a + b)) = O(h - l)、したがって、このパーティションは線形時間で動作します。

(私はここにバグがあると思います:a[i] <= a[l]がすべてl <= i <= hの場合はどうなりますか?)

1
Jasmijn

ネストされたループは、実際にはOとは無関係です。重要なのは、アイテムを処理する回数です。 generalでは、ループがネストを定義しますが、これは、掘り下げるとループが急速に小さくなる特殊なケースです。 (各反復で何らかの方法で非常に悪いピボットを選択した場合、ループは速く縮小せず、O(n ^ 2)ランタイムになります。リストの最初の項目を毎回選択して、すでにソートしようとしていることに注意してください-ソートされたリストがこれを引き起こします。)

0
Loren Pechtel