Merge Sortのこの実装を例としてみましょう
void mergesort(Item a[], int l, int r) {
if (r <= l) return;
int m = (r+l)/2;
mergesort(a, l, m); ------------ (1)
mergesort(a, m+1, r); ------------(2)
merge(a, l, m, r);
a)このマージソートの時間の複雑さはO(nlg(n))です。 (1)と(2)を並列化すると実用的な利点が得られますか?理論的には、それらを並列化した後、O(nlg(n)になってしまうように見えます。しかし、実際には利益を得ることができますか?
b)ここでのこのマージソートのスペースの複雑さはO(n)です。ただし、リンクリストを使用してインプレースマージソートを実行することを選択した場合(配列で合理的に実行できるかどうかはわかりません)、再帰スタックフレームサイズを考慮する必要があるため、スペースの複雑さがO(lg(n))になります? O(lg(n))は64を超えることはできないため、定数として扱うことができますか?いくつかの場所でこれを誤解している可能性があります。
c) http://www.cprogramming.com/tutorial/computersciencetheory/sortcomp.html は、マージソートではリンクリストを使用して一定のスペースが必要だと言います。どうやって ?彼らはO(lg(n) constant?
d)[より明確にするために追加]スペースの複雑さの計算のために、入力配列またはリストがすでにメモリにあると仮定するのは公平ですか?複雑さの計算を行うとき、入力によって既に使用されているスペース以外に必要な「余分な」スペースを常に計算します。それ以外の場合、スペースの複雑さは常にO(n)またはそれ以下になります。
a)はい-完璧な世界では、サイズn、n/2、n/4 ...のlog nのマージを行う必要があります(または、1、2、3 ... n/4、n/2 、n-並列化できません)、これによりO(n)が得られます。まだO(n log n)です。それほど完璧ではない世界では、プロセッサの数に制限はなく、コンテキスト切り替えと同期は潜在的な利益を相殺します。
b)要素をどこかに保存する必要があるため、スペースの複雑さは常にΩ(n)です。追加のスペースの複雑さは、配列を使用する実装ではO(n)、リンクリスト実装ではO(1)。リストを使用する実装では追加のスペースが必要です。リストポインターの場合は、メモリ内にリストがある場合を除き、問題ではありません。
編集スタックフレームをカウントする場合、それはO(n)+ O(log n)なので、配列の場合はまだO(n)です。 O(log n)の追加メモリです。
c)リストは、マージプロセス中に変更されたポインターのみを必要とします。それには一定の追加メモリが必要です。
d)それが、マージソートの複雑さの分析で、人々が「追加のスペース要件」またはそのようなものに言及する理由です。要素をどこかに保存する必要があることは明らかですが、純粋主義者を寄せ付けないためには、「追加のメモリ」に言及する方が常に良いです。
MergeSort time ComplexityはO(nlgn)これは基本的な知識です。MergeSortスペースの複雑さは常にO(n)配列を含む。描画する場合スペースツリーは、スペースの複雑さはO(nlgn)のように見えますが、コードは深さ優先のコードなので、ツリーの1つのブランチに沿ってのみ常に拡張されるため、必要なスペースの合計使用量は常にO(3n) = O(n)で区切られます。
たとえば、スペースツリーを描画すると、O(nlgn)のように見えます
16 | 16
/ \
/ \
/ \
/ \
8 8 | 16
/ \ / \
/ \ / \
4 4 4 4 | 16
/ \ / \ / \ / \
2 2 2 2..................... | 16
/ \ /\ ........................
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 | 16
ここで、ツリーの高さはO(logn) =>スペースの複雑さはO(nlogn + n)= O(nlogn)です。ただし、実際のコードではそうではないため、そうではありません。たとえば、N = 16の場合、これがmergesortのコードの実行方法ですN = 16.
16
/
8
/
4
/
2
/ \
1 1
使用されているスペースの数が32 = 2n = 2 * 16 <3nであることに注意してください
その後、上にマージします
16
/
8
/
4
/ \
2 2
/ \
1 1
34 <3nです。その後、上にマージします
16
/
8
/ \
4 4
/
2
/ \
1 1
36 <16 * 3 = 48
その後、上にマージします
16
/ \
8 8
/ \
4 4
/ \
2 2
/\
1 1
16 + 16 + 14 = 46 <3 * n = 48
大きい場合、n = 64
64
/ \
32 32
/ \
16 16
/ \
8 8
/ \
4 4
/ \
2 2
/\
1 1
64 * 3 <= 3 * n = 3 * 64
これは、一般的な場合の帰納法によって証明できます。
そのため、スペースの複雑さは、O(3n) = O(n)コードを並列に実行するのではなく、マージします。
私の実装の例を以下に示します。
templace<class X>
void mergesort(X a[], int n) // X is a type using templates
{
if (n==1)
{
return;
}
int q, p;
q = n/2;
p = n/2;
//if(n % 2 == 1) p++; // increment by 1
if(n & 0x1) p++; // increment by 1
// note: doing and operator is much faster in hardware than calculating the mod (%)
X b[q];
int i = 0;
for (i = 0; i < q; i++)
{
b[i] = a[i];
}
mergesort(b, i);
// do mergesort here to save space
// http://stackoverflow.com/questions/10342890/merge-sort-time-and-space-complexity/28641693#28641693
// After returning from previous mergesort only do you create the next array.
X c[p];
int k = 0;
for (int j = q; j < n; j++)
{
c[k] = a[j];
k++;
}
mergesort(c, k);
int r, s, t;
t = 0; r = 0; s = 0;
while( (r!= q) && (s != p))
{
if (b[r] <= c[s])
{
a[t] = b[r];
r++;
}
else
{
a[t] = c[s];
s++;
}
t++;
}
if (r==q)
{
while(s!=p)
{
a[t] = c[s];
s++;
t++;
}
}
else
{
while(r != q)
{
a[t] = b[r];
r++;
t++;
}
}
return;
}
これがお役に立てば幸いです!=)
すぐにチー・ローン、
トロント大学
a)はい、もちろん、マージソートの並列化は非常に有益です。 nlognのままですが、定数は大幅に低くする必要があります。
b)リンクリストのスペースの複雑さはO(n)である必要があります。具体的にはO(n) + O(logn)です。*ではなく+であることに注意してください。漸近解析を行うときは、定数に多く関心を持ちます。
c)漸近解析では、方程式の支配的な項のみが重要であるため、a *ではなく+があるという事実によりO(n)になります。サブリストをすべて複製している場合、O(nlogn) space-となると思いますが、スマートなリンクリストベースのマージソートはリストの領域を共有できます。
マージソートの最悪のパフォーマンス:O(n log n)、マージソートのベストケースのパフォーマンス:O(n log n)typicaly、O(n)ナチュラルバリアント、マージソートの平均パフォーマンス:O(n log n)、マージソートの最悪のスペースの複雑さ:О(n)合計、 O(n)補助
最良の場合と最悪の場合の両方で、複雑さはO(nlog(n))です。ただし、各ステップで余分なNサイズの配列が必要であるため、スペースの複雑度はO(n + n)でO(2n)です。複雑さを計算するための定数値を削除してO(n)
スペースの複雑さ:サブアレイ/サブリストが各レベルで作成される場合のnlogn(lognレベル*各レベルでnスペースが必要=> logn * n)。そうでない場合、スタックスペースが考慮されると、LinkedListの場合はlogn、Arrayの場合はn(n + logn = n)になります。時間の複雑さ:最悪の場合と平均的な場合のnlogn
マージソートスペースの複雑さはO(nlogn)
です。これは、最大O(logn)
再帰に進むことができ、再帰ごとにO(n)
の追加スペースがあることを考えると、非常に明白です。再割り当てが必要なマージされた配列を保存します。 O(n)
と言っている人には、スタックフレームの深さに達するためのO(n)
であることを忘れないでください。