web-dev-qa-db-ja.com

配列から値をフェッチする複雑さがO(1)になるのはなぜですか?

インデックスから配列から値をフェッチする複雑さがO(1)になるのはなぜですか?

アルゴリズムはすべてのインデックスを調べ、正しいインデックスを見つけ、どの値を返すかを知っている必要があると思いました。つまり、複雑さは平均してO(n/2)です。

なぜ実際にO(1)なのですか?

3
Aviv Cohn

配列アクセスがO(1)である理由を理解するための鍵は、それらが既知の長さを持っていることを理解することです。Cのような言語では、メモリの順次ブロックを割り当てることでこれを保証できます。それはメモリ内でシーケンシャルな場合、直接アドレス指定とそれによる直接アクセスのためにポインタ演算を使用できます。配列オブジェクトは、その内部編成と同様の原則に従いますが、実装はより複雑になる可能性があります。 O(1)時間、全体でO(1)時間を要する操作があります。この追加がCおよびC++でも少なくとも、配列内のすべての項目は同じ型である必要があります。これらの項目はすべて、このポインタ演算が機能するために同じバイト数を占有する必要があります。

Array Example

これを、リストの開始が定義されているシングルリンクリストと比較して、リストの次のノードを指すポインターまたは参照アドレスを指定することで、そのリストに追加します。各ノードには、リスト内の次のノードへのポインターとデータ項目という2つのものが含まれています。戻ることはできないので、リストから項目が必要になるたびに、それがどこにあるかが正確にわかっていても、リスト全体をその項目まで走査する必要があります。したがって、平均的なケースのアクセスではO(n/2)、最悪のケースではO(n)となります。これを行うことでこれを軽減できます。テールポインターとヘッドポインターのある二重リンクリストですが、実際にはランダムアクセスではなくシーケンシャルアクセスの形式に制限されています。最後のポインターをnullに設定して、コンピューターに基本的に同じことを伝えます配列のサイズを指定するときにそれを使用します。

Linked_List

14
World Engineer

配列のインデックスnはn + 1を指すため番目 配列の要素(ゼロベースのインデックスを使用)。いくつかの簡単な数学では、O(1)で目的の要素の正確な位置を計算できます。

enter image description here

さらに読む
Java配列

17
Robert Harvey

簡単な答え:

_453 + 6_もO(1)時間で計算できます。

いくつかのコードを書いて、より詳しく見てみましょう。

_#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  int index = atoi(argv[1]);
  int a[10];
  for(int i = 0; i < 10; i++) { a[i] = 42 + i; }

  printf("foo!\n");
  int val = a[index];
  printf("%d\n",val);
  return 0;
}
_

これを_gcc -S_を介して実行すると、アセンブリを取得して、より詳しく調べることができます。

fooがそこにあるのは、2つのprintfの間のスポットをすばやく見つけられるようにするためです。

_    leaq    L_.str(%rip), %rdi
    movb    $0, %al
    callq   _printf
    leaq    L_.str1(%rip), %rdi
    movslq  -28(%rbp), %rcx
    movl    -80(%rbp,%rcx,4), %edx
    movl    %edx, -88(%rbp)
    movl    -88(%rbp), %esi
    movl    %eax, -92(%rbp)         ## 4-byte Spill
    movb    $0, %al
    callq   _printf
_

免責事項:私は議会の人ではありません。MIPSで働いてから数十年が経過し、インテルを一目見ただけでした。私はこれを間違っているかもしれません。実際にはgccではなく、clangを実行したため、これはシステムによって異なる場合があります。

最初のprintfが完了すると、str1(_"%d\n_)のフォーマットのアドレスをロードすることから始めます。含まれていない少し上から、-28(%rbp)atoi呼び出しの結果です。これは、システムがすべてを最適化しようとするのを回避するためにあります(なぜなら それが実行されます のため)。そして、それはまた、他のいくつかの動き回ります。

ただし、ループはありません。探しているインデックスに関係なく、一定数の命令で実行されます。これがO(1)の定義です。

最初の部分、配列の母集団を見てみましょう。

_        movl    $0, -84(%rbp)
LBB0_1:                                 ## =>This Inner Loop Header: Depth=1
        cmpl    $10, -84(%rbp)
        jge     LBB0_4
## BB#2:                                ##   in Loop: Header=BB0_1 Depth=1
        movl    -84(%rbp), %eax
        addl    $42, %eax
        movslq  -84(%rbp), %rcx
        movl    %eax, -80(%rbp,%rcx,4)
## BB#3:                                ##   in Loop: Header=BB0_1 Depth=1
        movl    -84(%rbp), %eax
        addl    $1, %eax
        movl    %eax, -84(%rbp)
        jmp     LBB0_1
LBB0_4:
        leaq    L_.str(%rip), %rdi
        movb    $0, %al
        callq   _printf
_

そして、配列を埋めるループを見ることができると聞きます。比較、ラベル、ものへのダウンよりも大きい場合のジャンプ、および1をインクリメントした後の先頭へのジャンプがあります。

これが実行される回数は、配列のサイズ(この場合は10)によって異なります。すべてのインデックスを反復するループは1つだけです。それがO(N)です。

これが配列ではなくリンクリストである場合、nを見つけるためにリンクリストをたどる必要があります。番目 素子。それからnに行く番目 要素もO(N)になります。 その他のデータ構造 -を使用して、リンクリストの特定の要素にすばやくアクセスする方法がありますが、それでもO(1)ではありません。

4
user40980