インデックスから配列から値をフェッチする複雑さがO(1)になるのはなぜですか?
アルゴリズムはすべてのインデックスを調べ、正しいインデックスを見つけ、どの値を返すかを知っている必要があると思いました。つまり、複雑さは平均してO(n/2)です。
なぜ実際にO(1)なのですか?
配列アクセスがO(1)である理由を理解するための鍵は、それらが既知の長さを持っていることを理解することです。Cのような言語では、メモリの順次ブロックを割り当てることでこれを保証できます。それはメモリ内でシーケンシャルな場合、直接アドレス指定とそれによる直接アクセスのためにポインタ演算を使用できます。配列オブジェクトは、その内部編成と同様の原則に従いますが、実装はより複雑になる可能性があります。 O(1)時間、全体でO(1)時間を要する操作があります。この追加がCおよびC++でも少なくとも、配列内のすべての項目は同じ型である必要があります。これらの項目はすべて、このポインタ演算が機能するために同じバイト数を占有する必要があります。
これを、リストの開始が定義されているシングルリンクリストと比較して、リストの次のノードを指すポインターまたは参照アドレスを指定することで、そのリストに追加します。各ノードには、リスト内の次のノードへのポインターとデータ項目という2つのものが含まれています。戻ることはできないので、リストから項目が必要になるたびに、それがどこにあるかが正確にわかっていても、リスト全体をその項目まで走査する必要があります。したがって、平均的なケースのアクセスではO(n/2)、最悪のケースではO(n)となります。これを行うことでこれを軽減できます。テールポインターとヘッドポインターのある二重リンクリストですが、実際にはランダムアクセスではなくシーケンシャルアクセスの形式に制限されています。最後のポインターをnullに設定して、コンピューターに基本的に同じことを伝えます配列のサイズを指定するときにそれを使用します。
配列のインデックスn
はn + 1を指すため番目 配列の要素(ゼロベースのインデックスを使用)。いくつかの簡単な数学では、O(1)
で目的の要素の正確な位置を計算できます。
さらに読む
Java配列
簡単な答え:
_
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)ではありません。