web-dev-qa-db-ja.com

配列ベースのスタックとリストベースのスタックおよびキュー

配列とリンクリストの両方として実装した場合のスタック操作とキュー操作の成長率(実行時とスペースの両方)を比較しようとしています。これまでのところ、キューpop() sの平均ケースランタイムのみを見つけることができましたが、これら2つのデータ構造を包括的に調査し、ランタイム/スペースの動作を比較するものはありません。

具体的には、キューとスタックの両方についてPush()pop()を比較したいと考えています。both配列とリンクリストとして実装されています(したがって2つの操作x 2構造x 2実装、または8つの値)。

さらに、これらの両方のベスト、平均、最悪のケース値、およびそれらが消費するスペースの量に関連するものを高く評価します。

私が見つけた最も近いものは、この「すべてのcsチートシートの母」pdfです。これは明らかに、高度なアルゴリズムと離散機能の修士レベルまたは博士レベルのチートシートです。

スタックベースとキューの両方で、アレイベースの実装とリストベースの実装をいつ、どこで使用すべきかを判断する方法を探しています。

43
IAmYourFaja

リンクリストと配列を使用してキューとスタックを実装する方法は複数ありますが、どの方法を探しているのかわかりません。ただし、これらの構造を分析する前に、上記のデータ構造に関する実行時の重要な考慮事項を確認しましょう。

ヘッドポインターだけの単一リンクリストでは、値の先頭に追加するコストはO(1)です。新しい要素を作成し、ポインターをリストの古いヘッドを指すように配線します。次に、ヘッドポインターを更新します。最初の要素を削除するコストもO(1)です。これは、現在のヘッドの後の要素を指すようにヘッドポインタを更新し、古いヘッドのメモリを解放することによって行われます(明示的なメモリ管理が実行される場合)。ただし、これらのO(1)項の定数係数は、動的割り当ての費用のために高くなる場合があります。リンクリストのメモリオーバーヘッドは、通常、各要素に追加のポインターが格納されるため、O(n)合計追加メモリです。

動的配列では、O(1)時間内に任意の要素にアクセスできます。また、 amortized O(1) に要素を追加することもできます。つまり、n回の挿入の合計時間はO(n)ですが、挿入の実際の時間境界はかなり長い場合があります悪い。通常、動的配列は、ほとんどの挿入が事前に割り当てられたスペースに追加することでO(1)を取るが、配列容量を2倍にして要素をコピーすることによりΘ(n)時間で実行される少数の挿入を持つことで実装されます。余分なスペースを割り当てて要素を遅延コピーすることでこれを削減しようとするテクニックがあります(たとえば、 このデータ構造を参照してください )。通常、動的配列のメモリ使用量は非常に良好です。たとえば、配列が完全にいっぱいの場合、O(1)のオーバーヘッドが余分にありますが、配列のサイズが2倍になった直後は配列に割り当てられたO(n)未使用要素。割り当てはまれであり、アクセスは高速であるため、通常、動的配列はリンクリストよりも高速です。

それでは、リンクリストまたは動的配列を使用してスタックとキューを実装する方法について考えてみましょう。これを行うには多くの方法がありますので、次の実装を使用していると仮定します。

それぞれを順番に考えてみましょう。

一重リンクリストに裏打ちされたスタック。一重リンクリストはO(1) time prepend and delete-firstをサポートしているため、リンクリストに裏打ちされたスタックにプッシュまたはポップするコストもO(1)最悪の場合です。ただし、新しい要素を追加するたびに新しい割り当てが必要になり、他の操作に比べて割り当てが高くなる可能性があります。

動的配列に裏打ちされたスタック。スタックへのプッシュは、動的配列に新しい要素を追加することで実装できます。これは、償却されたO(1)時間および最悪の場合O(n)時間。スタックからのポップは、最悪の場合O(1)(または回収しようとする場合は償却O(1))で実行される最後の要素を削除するだけで実装できます。未使用のスペース)。言い換えれば、最も一般的な実装には、ベストケースのO(1)プッシュとポップ、最悪のケースO(n)プッシュとO(1)があります。 _ポップ、および償却O(1)プッシュおよびO(1)ポップ。

一重リンクリストによってバッキングされたキュー。一重リンクリストの後ろに追加することで、リンクリストへのエンキューを実装できます。 -ケースタイムO(1)。デキューは、最初の要素を削除することで実装できますが、これには最悪の場合の時間O(1)もかかります。また、エンキューごとに新しい割り当てが必要になりますが、時間がかかる場合があります。

増大する循環バッファに支えられたキュー。循環バッファへのエンキューは、循環バッファの次の空き位置に何かを挿入することで機能します。これは、必要に応じて配列を拡大し、新しい要素を挿入することで機能します。動的配列の同様の分析を使用すると、これにはベストケース時間O(1)、ワーストケース時間O(n)、および償却時間O(1)がかかります。バッファからのデキューは、循環バッファの最初の要素を削除することで機能します。これは、最悪の場合O(1)時間がかかります。

要約すると、すべての構造はO(n)時間でn個の要素をプッシュおよびポップすることをサポートします。リンクリストバージョンは、最悪の場合の動作が優れていますが、実行される割り当ての数が原因で、全体的なランタイムが低下する可能性があります。アレイのバージョンは最悪の場合は遅くなりますが、操作ごとの時間がそれほど重要でなければ、全体的なパフォーマンスは向上します。

スタックを実装するために検討したい別のオプションは、 VList 、リンクリストと動的配列のハイブリッドである最近のデータ構造です。リンクリストよりも割り当てが少なくなり、ポインタが少なくなりますが、最悪の場合はスペース使用量が少し高くなります。また、スタックとキューの両方に使用できる配列とリンクリストの別のハイブリッドであるチャンクリストを調べることもできます。

お役に立てれば!

78
templatetypedef

あなたの質問を誤解した場合は申し訳ありませんが、私がそうしなかった場合は、これがあなたが探している答えだと思います。

ベクトルを使用すると、コンテナの最後にある要素のみを効率的に追加/削除できます。両端キューを使用すると、コンテナの最初/最後に要素を効率的に追加/削除できます。リストを使用すると、コンテナ内のどこにでも要素を効率的に挿入/削除できます。

vectors/dequeは、ランダムアクセス反復子を許可します。リストは順次アクセスのみを許可します。

データの使用方法と保存方法は、どちらが最も適切かを判断する方法です。

編集:

これにはさらに多くのことがありますが、私の答えは非常に一般化されています。あなたの質問が何であるかを追跡しさえすれば、私はより深く入り込むことができます。

0
user898058