web-dev-qa-db-ja.com

他のデータ構造の代わりに配列を使用するのはなぜですか?

私がプログラミングを行っていたとき、情報を保存するのに配列が他の形式よりも優れているという例を見たことはありません。実際、プログラミング言語に追加された「機能」がこれを改善し、それによってそれらに取って代わったと考えていました。言うまでもなく、それらは置き換えられるのではなく、むしろ新しい命を与えられることがわかります。

それで、基本的に、配列を使用する意味は何ですか?

これは、コンピューターの観点から配列を使用する理由ではなく、プログラミングの観点から配列を使用する理由です(微妙な違い)。コンピューターがアレイで行うことは、問題のポイントではありませんでした。

191
Xesaniel

レッスンの時間に戻る時間。今日、これらのことについてはファンシーなマネージ言語ではあまり考えていませんが、同じ基盤の上に構築されているため、Cでメモリがどのように管理されるかを見てみましょう。

飛び込む前に、「ポインター」という用語の意味を簡単に説明します。ポインタは、メモリ内の場所を「指す」単なる変数です。このメモリ領域の実際の値は含まれておらず、メモリアドレスが含まれています。メモリブロックをメールボックスと考えてください。ポインターは、そのメールボックスへのアドレスになります。

Cでは、配列は単にオフセットのあるポインターであり、オフセットはメモリ内のどこまで見るかを指定します。これにより、 O(1) アクセス時間が提供されます。

  MyArray   [5]
     ^       ^
  Pointer  Offset

他のすべてのデータ構造は、これに基づいて構築されるか、ストレージに隣接メモリを使用しないため、ランダムアクセスのルックアップ時間が低下します(シーケンシャルメモリを使用しないことには他の利点もあります)。

たとえば、6つの数字(6,4,2,3,1,5)の配列があるとします。メモリ内では次のようになります。

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================

配列では、各要素がメモリ内で隣り合っていることがわかります。 C配列(ここではMyArrayと呼ばれます)は、単に最初の要素へのポインターです。

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   ^
MyArray

MyArray [4]を検索する場合、内部的に次のようにアクセスします。

   0     1     2     3     4 
=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
                           ^
MyArray + 4 ---------------/
(Pointer + Offset)

ポインターにオフセットを追加することで配列内の任意の要素に直接アクセスできるため、配列のサイズに関係なく、同じ時間で任意の要素を検索できます。つまり、MyArray [1000]の取得には、MyArray [5]の取得と同じ時間がかかります。

別のデータ構造は、リンクリストです。これは、それぞれが次のノードを指すポインターの線形リストです

========    ========    ========    ========    ========
| Data |    | Data |    | Data |    | Data |    | Data |
|      | -> |      | -> |      | -> |      | -> |      | 
|  P1  |    |  P2  |    |  P3  |    |  P4  |    |  P5  |        
========    ========    ========    ========    ========

P(X) stands for Pointer to next node.

各「ノード」を独自のブロックにしたことに注意してください。これは、メモリ内で隣接していることが保証されていないためです(ほとんどの場合、隣接することはありません)。

P3にアクセスしたい場合、メモリ内のどこにあるのかわからないため、直接アクセスできません。私が知っているのはルート(P1)がどこにあるかだけなので、代わりにP1から開始し、目的のノードへの各ポインターに従う必要があります。

これは、O(N)ルックアップ時間です(各要素が追加されると、ルックアップコストが増加します)。 P4に到達するよりもP1000に到達する方がはるかに高価です。

ハッシュテーブル、スタック、キューなどの高レベルのデータ構造はすべて、内部的に配列(または複数の配列)を使用できますが、リンクリストとバイナリツリーは通常、ノードとポインターを使用します。

配列を使用するだけでなく、値を検索するために線形トラバースを必要とするデータ構造を誰が使用するのか疑問に思うかもしれませんが、それらには用途があります。

再びアレイを取ります。今回は、値「5」を保持する配列要素を見つけたいです。

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   ^     ^     ^     ^     ^   FOUND!

この状況では、ポインターを見つけるためにポインターに追加するオフセットがわからないので、0から始めて、それが見つかるまで進めなければなりません。これは、6つのチェックを実行する必要があることを意味します。

このため、配列内の値の検索はO(N)と見なされます。配列が大きくなると、検索のコストが増加します。

上記で、非シーケンシャルデータ構造を使用すると利点がある場合があると言ったことを思い出してください。データの検索はこれらの利点の1つであり、最良の例の1つはバイナリツリーです。

バイナリツリーはリンクリストに似たデータ構造ですが、単一のノードにリンクする代わりに、各ノードは2つの子ノードにリンクできます。

         ==========
         |  Root  |         
         ==========
        /          \ 
  =========       =========
  | Child |       | Child |
  =========       =========
                  /       \
            =========    =========
            | Child |    | Child |
            =========    =========

 Assume that each connector is really a Pointer

データがバイナリツリーに挿入されると、いくつかのルールを使用して、新しいノードを配置する場所が決定されます。基本的な概念は、新しい値が親よりも大きい場合は左に挿入し、それより低い場合は右に挿入するというものです。

つまり、バイナリツリーの値は次のようになります。

         ==========
         |   100  |         
         ==========
        /          \ 
  =========       =========
  |  200  |       |   50  |
  =========       =========
                  /       \
            =========    =========
            |   75  |    |   25  |
            =========    =========

値75のバイナリツリーを検索するとき、次の構造のために3つのノード(O(log N))を訪問するだけで済みます。

  • 75は100未満ですか?正しいノードを見る
  • 75は50よりも大きいですか?左ノードを見てください
  • 75があります!

ツリーには5つのノードがありますが、残りの2つを調べる必要はありません。なぜなら、それら(およびその子)が探している値を含むことができないことを知っていたからです。これにより、最悪の場合はすべてのノードにアクセスする必要があることを意味しますが、最良の場合はノードのごく一部にアクセスするだけで済みます。

これが、配列がビートを取得する場所であり、O(N)アクセス時間にもかかわらず、線形のO(1)検索時間を提供します。

これは、メモリ内のデータ構造に関する非常に高レベルの概要であり、多くの詳細をスキップしますが、他のデータ構造と比較した配列の長所と短所を示していることが望ましいです。

765
FlySwat

O(1)ランダムアクセスの場合、これは無効にすることはできません。

73
jason

すべてのプログラムが同じことをしたり、同じハードウェアで実行したりするわけではありません。

これは通常、さまざまな言語機能が存在する理由の答えです。配列は、コンピュータサイエンスの中核概念です。配列をリスト/行列/ベクトル/高度なデータ構造に置き換えると、パフォーマンスに深刻な影響を与え、多くのシステムではまったく実行不可能です。問題のプログラムのために、これらの「高度な」データ収集オブジェクトのいずれかを使用する必要がある場合は、いくつでもあります。

ビジネスプログラミング(ほとんどの場合)では、比較的強力なハードウェアをターゲットにできます。 C#のリストまたはJavaのベクターを使用することは、これらの構造では開発者が目標をより早く達成できるため、この種のソフトウェアをより多く機能させるための正しい選択です。

組み込みソフトウェアやオペレーティングシステムを作成する場合、多くの場合、アレイの方が適切な選択です。配列の機能は少なくなりますが、RAMの使用量が少なくなり、コンパイラーは配列を検索するためにコードをより効率的に最適化できます。

私はこれらのケースの多くの利点を除外していると確信していますが、あなたがポイントを得るよう願っています。

21
Jason Jackson

配列の利点を調べる方法は、配列のO(1)アクセス機能が必要であり、したがって大文字であるかを確認することです。

  1. アプリケーションのルックアップテーブル(特定のカテゴリレスポンスにアクセスするための静的配列)

  2. メモ化(既に計算された複雑な関数の結果、関数値を再度計算しないように、たとえばlog x)

  3. 画像処理を必要とする高速コンピュータービジョンアプリケーション( https://en.wikipedia.org/wiki/Lookup_table#Lookup_tables_in_image_processing

0
priya khokher