web-dev-qa-db-ja.com

配列とスタックの違いは何ですか?

ウィキペディアによれば、 スタック

後入れ先出し(LIFO)の抽象データ型と線形データ構造です。

While an array

要素(値または変数)のコレクションで構成されるデータ構造であり、それぞれが少なくとも1つの配列インデックスまたはキーによって識別されます。

私が理解している限り、それらはかなり似ています。では、主な違いは何ですか?それらが同じでない場合、配列がスタックでできないこと、およびその逆ができることは何ですか?

10
Dynamic

まあ、あなたは確かに配列でスタックを実装することができます。違いはアクセスです。配列には、要素のリストがあり、いつでも任意の要素にアクセスできます。 (一連の木製のブロックがすべて並んでいると考えてください。)

しかし、スタックにはランダムアクセス操作はありません。 PushPeekおよびPopのみがあり、これらはすべてスタックの最上位の要素のみを処理します。 (木製のブロックが垂直に積み重なっていることを考えてください。タワーの上部の下には何も触れられないか、倒れます。)

45
Mason Wheeler

純粋なスタックでは、許可される演算はPushPop、およびPeekのみですが、実際には、それは厳密には当てはまりません。むしろ、Peek操作を使用すると、スタック上の任意の位置を確認できることがよくありますが、問題は、スタックの一方の端を基準にしていることです。

したがって、他の人が言ったように、配列はランダムアクセスであり、すべてが配列の先頭を参照です。

スタックでは、スタックのワーキングエンドでのみ追加/削除できますが、ランダムアクセスの読み取りただし、ワーキングエンドで参照されますがあります。それが根本的な違いです。

たとえば、スタックのパラメーターを関数に渡す場合、呼び出し先はパラメーターを確認するためにパラメーターをポップする必要はありません。単にローカル変数をスタックにプッシュし、スタックポインターからのオフセットに基づいてすべてのローカル変数とパラメーターを参照します。配列だけを使用している場合、呼び出し先はどのようにしてそのパラメータを探す場所を知るのでしょうか?呼び出し先が完了すると、呼び出し先はローカル変数をポップし、戻り値をプッシュし、呼び出し元に制御を返し、呼び出し元は戻り値(ある場合)をポップし、パラメータをスタックからポップします。美しさは、関数の呼び出しにどれだけネストされていても機能することです(スタック領域が不足しない場合)。

これは特定の使用/実装の1つですが、違いを示しています。配列は常に最初から参照されますが、スタックは常にいくつかの作業終了位置から参照されます。

スタックの可能な実装の1つは、配列plusであり、作業端がどこにあるかを覚えておくためのインデックスです。

6
Scott Whitlock

ここで起こっている最大の混乱は、実装と基本的なデータ構造です。

ほとんどの(より基本的な言語の)配列は、任意の時点でアクセスできる固定長の要素のセットを表します。このような要素がたくさんあるという事実は、それがどのように使用されることになっているのかを何も知らない(そして、率直に言って、コンピュータは、使用法に違反しない限り、その使用方法を知りません/気にしません)。

スタックは、特定の方法で処理する必要があるデータを表すために使用される抽象化です。これは抽象的な概念です。これは、上部に追加したり、上部から削除したりできるサブルーチン/メソッド/関数が必要であり、上部のデータは変更されないためです。この方法でアレイを使用するという純粋にあなたの選択。

配列(いくつかの最大サイズを含む)、動的配列(スペースがなくなると大きくなる可能性があります)、リンクリストなど、さまざまな種類のデータ構造からスタックを作成できます。個人的には、リンクされたリストがスタックの制限を最もよく表していると感じます。最初の要素を超えて物事を見るのに少し労力を費やさなければならず、前面に追加したり前面から削除したりするのは非常に簡単です。

したがって、配列を使用してスタックを作成できますが、それらは同等ではありません

4
chrisjleaf

A\arrayの任意のインデックスからアイテムを取得できます。

スタックでは、別のスタックBを使用して、スタックAの中央にあるアイテムを取得できます。

一番上のアイテムをAから取り出し、Aの目的のアイテムに到達するまでBに入れてから、BのアイテムをスタックAの一番上に戻します。

したがって、任意のインデックスを取得する機能を必要とするデータの場合、スタックの操作はより困難になります。

「後入れ先出し」動作が必要な状況では、スタックの方が配列よりオーバーヘッドが少なくなります。

3
Jesse Black

彼らの責任は異なります:

  • スタックは要素をスタックにポップし、要素をスタックからプッシュできる必要があります。そのため、通常はPop()Push()を持っています。

  • 配列の責任は、指定されたインデックスで要素を取得/設定することです

3
CodeART

配列は、プログラマーの観点から、場所とサイズが固定されており、自分がどこにいて、全体がどこにあるかがわかります。すべてにアクセスできます。

スタックを使用すると、スタックの一方の端に座っても、そのサイズや安全にどこまで移動できるかがわかりません。それへのアクセスは、割り当てた量に制限されます。ヒープまたはプログラム空間にたどり着いただけの場合、必要な量を割り当てるときに、そのことさえわからないことがよくあります。スタックのビューは、自分で割り当てた小さな配列で、必要なサイズであり、制御して確認できます。部分は配列と同じです。違いは、引数と用語のために、配列が別の配列の一方の端に貼り付けられていることです。これは、可視性がなく、どの程度大きくても小さくても、害を及ぼすことなく触れることができません。配列は、グローバルでない限り、とにかくスタックに実装されることが多いため、その関数の実行中、配列とスタックは同じスペースを共有します。

ハードウェア側に行きたい場合、それはもちろんプロセッサー固有ですが、一般に配列は既知の開始点/アドレスに基づいており、サイズはコンパイラー/プログラマーに知られており、アドレスはそれに計算されます、レジスタオフセットアドレッシングを使用する(このベースレジスタ値とこのオフセットレジスタ値で定義されたアドレスから値をロードします。同様に、コンパイルすると、必ずしもレジスタベースではない即時のオフセットになる可能性があります。もちろんプロセッサに依存します)。高レベルのコードで配列にアクセスすることに似ています。スタックと同様に、利用可能な場合は、レジスタまたはイミディエートオフセットアドレス指定を使用できますが、多くの場合、スタックポインター自体、またはそのためのスタックフレームへのアクセスに使用するためにコンパイラー/プログラマーによって予約されたレジスターのいずれかの特殊レジスターを使用します関数。また、一部のプロセッサでは、スタックにアクセスするために特別なスタックアクセス関数が使用または要求されます。あなたはプッシュとポップの指示を持っていますが、それらはあなたが考えるほど頻繁には使用されておらず、実際にはこの質問には当てはまりません。一部のプロセッサでは、プッシュとポップはスタック上のスタックポインタだけでなく、どこのレジスタでも使用できる命令のエイリアスであり、この質問に関連するプッシュとポップをさらに削除します。

0
old_timer

それらは「非常に似ている」とまでは言いません。

配列は、後で順次またはインデックスを介してアクセスされるものを保持するために使用されます。データ構造は、あらゆる種類のアクセス方法(FIFO、LIFO、FILOなど)を意味しませんが、必要に応じてその方法で使用できます。

スタックは、生成されたものを追跡する方法です。作成されるスタックのタイプに応じて、アクセス方法が暗黙的または必須になります。フレームスタックはLIFO=の例です。免責事項-ここでデータ構造分類法を混合している可能性があり、スタックは本当にLIFOのみを許可する可能性があります。それ以外の場合は、異なるタイプのキューになります。

そのため、配列をスタックとして使用することはできますが(望んでいませんが)、スタックを配列として使用することはできません(本当に一生懸命に努力しない限り)。

0
user53019

配列内のデータには、キーまたはインデックスでアクセスできます。スタックのデータは、スタックの最上部からポップされるとアクセスできます。これは、そのインデックスがわかっていれば、配列からのデータへのアクセスが簡単であることを意味します。スタックからデータにアクセスするということは、探している要素が見つかるまで要素をポップし続ける必要があることを意味します。つまり、データアクセスに時間がかかる可能性があります。