web-dev-qa-db-ja.com

スタックを実装するためのリンクリストと動的配列

学校の最終学年が始まる前に、データ構造とアルゴリズムのレビューを開始し、すべてを把握していることを確認しました。あるレビューの問題は、「リンクリストまたは動的配列を使用してスタックを実装し、なぜ最良の選択をしたのかを説明する」と述べています。

スタックのサイズを頻繁に変更する必要がある場合があるため、テールポインター付きのリストを使用してスタックを実装する方が直感的に思えました。大量のデータの場合、動的配列のサイズ変更はコストのかかる操作であるため、リストの方が適しているようです。さらに、リストを使用すると、実際に必要なスペースよりも多くのスペースを割り当てる必要がないため、スペース効率が向上します。

ただし、動的配列を使用すると、データをはるかに迅速に追加できます(サイズを変更する必要がある場合を除く)。ただし、配列を使用する方が全体的に速いのか、それともサイズを変更する必要がないのかはわかりません。

この本の解決策は「非常に大きなオブジェクトを格納するためには、リストがより良い実装である」と述べていますが、その理由はわかりません。

どちらが最適ですか?どの実装が「最良」であるかを決定するためにどのような要素を使用する必要がありますか?また、私の論理のいずれかがここにありますか?

29
Casey Patton

ここには多くのトレードオフが関係しており、この質問に対する「正しい」答えはないと思います。

テールポインタ付きのリンクリストを使用してスタックを実装する場合、プッシュ、ポップ、またはピークの最悪の場合のランタイムはO(1)です。ただし、各要素には追加のオーバーヘッド(つまり、ポインター)が関連付けられます。つまり、構造には常にO(n)オーバーヘッドがあります。さらに、速度に応じてメモリアロケータでは、スタックに新しいノードを割り当てるコストが顕著になる可能性があります。また、スタックからすべての要素を継続的にポップオフすると、リンクされていることが保証されないため、ローカリティが低いためにパフォーマンスが低下する可能性があります。リストセルはメモリに連続して保存されます。

動的配列を使用してスタックを実装する場合、プッシュまたはポップするためのamortizedランタイムはO(1)であり、ピークの最悪の場合のコストはOです。 (1)これは、スタック内の単一の操作のコストを気にする場合、これは最善のアプローチではない可能性があることを意味しますが、割り当ては頻繁ではないため、n個の要素を追加または削除するための合計コストはリンクリストベースのアプローチの対応するコストよりも高速です。さらに、このアプローチのメモリオーバーヘッドは、通常、リンクリストのメモリオーバーヘッドよりも優れています。動的配列が要素へのポインタを格納するだけの場合、メモリオーバーヘッドは最悪の場合は、要素の半分が入力されたときに発生します。この場合、n個の追加のポインターがあり(リンクリストを使用した場合と同じ)、最良の場合、動的配列がいっぱいの場合はありません。空のセルと余分なオーバーヘッドはO(1)です。一方、動的配列に要素tが直接含まれている場合最悪の場合、メモリのオーバーヘッドが悪化する可能性があります。最後に、要素は連続して格納されるため、すべての要素がメモリ内で互いに隣接しているため、スタックから要素を継続的にプッシュまたはポップする場合は、より適切な局所性があります。

要するに:

  • リンクリストアプローチには、各操作で最悪の場合のO(1)保証があります。動的配列はO(1)保証を償却しました。
  • リンクリストの局所性は、動的配列の局所性ほど良くありません。
  • 両方が要素へのポインタを格納していると仮定すると、動的配列の合計オーバーヘッドは、リンクリストの合計オーバーヘッドよりも小さくなる可能性があります。
  • 要素が直接格納されている場合、動的配列の合計オーバーヘッドは、リンクリストのオーバーヘッドよりも大きくなる可能性があります。

これらの構造はどちらも、他の構造よりも明らかに「優れている」わけではありません。それは本当にあなたのユースケースに依存します。どちらが速いかを判断する最良の方法は、両方の時間を計り、どちらがより優れているかを確認することです。

お役に立てれば!

30
templatetypedef

実装を適切に設計すれば、動的配列のサイズ変更はコストのかかる作業ではありません。

たとえば、配列を大きくするには、配列がいっぱいの場合、2倍のサイズの新しい配列を作成し、アイテムをコピーします。

N個のアイテムを追加すると、最大3Nの償却費が発生します。

1
Mario Souza

さて、小さなオブジェクトと大きなオブジェクトの質問については、スタックに小さなオブジェクトがある場合に、リンクリストに使用する余分なスペースを検討してください。次に、スタックにlargeオブジェクトがたくさんある場合に、どれだけの追加スペースが必要になるかを検討します。

次に、同じ質問を検討しますが、動的配列に基づく実装を使用します。

1
Pillsy

あなたは自分で質問に答えたと思います。アイテムの数が多いスタックの場合、スタックの一番上にアイテムを追加するだけでは、動的配列に過度のオーバーヘッドコスト(コピーオーバーヘッド)が発生します。リストを使用すると、ポインタを簡単に切り替えることができます。

1
David Cheung

重要なのは、タスクの実行中にmalloc()が呼び出される回数です。メモリのブロックを取得するには、数百から数千の命令が必要になる場合があります。 (free()またはGCの時間は、それに比例する必要があります。)また、遠近感を保ちます。これは、他に何が起こっているかに応じて、合計時間の99%、または1%にすぎない可能性があります。

1
Mike Dunlavey