std :: dequeは実際には最初に一定時間の挿入を持っていますか?


8

標準は言う:

両端キューは、ランダムアクセス反復子(27.2.7)をサポートするシーケンスコンテナーです。さらに、最初または最後の一定時間の挿入および消去操作をサポートします。途中で挿入と消去には線形時間がかかります。

ただし、同じ条項にも次のように記載されています。

この条項のすべての複雑さの要件は、含まれているオブジェクトに対する操作の数に関してのみ記述されています。[例:vector<vector<int>>含まれるそれぞれのコピーの複雑さvector<int>自体は線形であるにもかかわらず、タイプのコピーコンストラクターは線形の複雑さを持っています。—最後の例]

これは、たとえば両端キューに既に存在するsと挿入される新しいオブジェクトに対して一定数以上の操作を実行しない限り、最初の挿入にdeque<int>線形時間がかかることを意味するのではありませんか?intint

たとえば、「サイズKのベクターのベクター」を使用して両端キューを実装するとします。最初にK回挿入するたびに、新しいサイズKのベクトルを最初に追加する必要があるため、他のすべてのサイズKのベクトルを移動する必要があります。これは、最初の挿入の時間の複雑さが償却されたO(N / K)であることを意味します。ここで、Nは要素の総数ですが、Kは定数なので、これは単なるO(N)です。しかし、これは規格で許可されているようです。サイズKのベクトルを移動してもその要素は移動せず、「複雑さの要件」は含まれているintオブジェクトの「操作の数に関してのみ述べられている」ためです。

規格はこれを本当に許可していますか?それとも、より厳しい要件、つまり、含まれているオブジェクトに対する一定数の操作に加えて一定の追加時間があると解釈する必要がありますか?


2
あなたの解釈はもっともらしいようです。火にさらに燃料を追加するには、[deque.modifiers]が両端キューの中央に挿入されているため、すべての反復子と両端キューの要素への参照が無効になります。両端キューの両端に挿入すると、両端キューへのすべてのイテレータが無効になりますが、両端キューの要素への参照の有効性には影響しません。移動するとイテレータは無効になりますが、参照は無効にならないので、ベクターの例のように機能します。
NathanOliver

この例は少しわかりにくいと思います。なぜなら、 "linear"を一度に線形の要素の数に関して線形に使用しvector<vector<int>>、次に内部の要素に対して線形を使用するためvector<int>です。外側のベクトルの要素の数のみを考慮する場合、内側のベクトルを定数としてコピーすることを検討しますが、私は間違っているかもしれませんが、すでにここで遅い
idclev 463035818

回答:


2

たとえば、「サイズKのベクターのベクター」を使用して両端キューを実装するとします。

それは有効な実装ではありません。の前に挿入するvectorと、コンテナ内のすべてのポインタ/参照が無効になります。フロント挿入時にポインター/参照をdeque無効にしないようにする必要があります。

しかし、今はそれを無視しましょう。

しかし、これは規格で許可されているようです。サイズKのベクトルを移動してもその要素は移動せず、「複雑さの要件」は、含まれるintオブジェクトに対する「操作の数に関してのみ示される」ためです。 。

はい、それは許可されます。実際、の実際の実装はdequeそれほどstd::vector明確ではありません(ただし、明らかな理由でそれ自体を使用することはありません)。deque実装の大まかな概要は、ブロックへのポインターの配列であり(前面と背面の両方に拡張用のスペースがあります)、各ブロックには最大X個のアイテムと次の/前のブロックへのポインター(単一の要素の反復が速い)。

前後に十分な数の要素を挿入すると、ブロックポインターの配列が大きくなる必要があります。そのためにはdeque、内のアイテム数に比例した線形時間の操作が必要ですが、実際にはアイテム自体に対しては操作されないため、カウントされません。仕様には、その操作の複雑さについては何も述べられていません。

この規定がないと、ユニークな機能セットdeque(高速なフロント/バック挿入ランダムアクセス)を実装できるかどうかわかりません。


「両端キューの両端に挿入すると、両端キューへのすべてのイテレータが無効になりますが、両端キューの要素への参照の有効性には影響しません。」
ブライアン

明らかに、前部または後部への挿入は時々線形時間を要します。しかし、ポインタの配列に実際に先頭にスペースがある場合、時間は定数で償却されます。前面にスペースがない場合、時間は償却定数ではありません。後者が許可されるかどうかを尋ねています。
ブライアン

@Brian:「しかし、ポインタの配列が実際に前にスペースを持っていない場合は、時間が一定の償却あるべき」何「ではありません一定の償却」を意味します。これは、非定数でなければならないときのN間の距離が常に増加していることを意味します。場合vector自動サイズ変更のの実装のみ追加項目の定数を加え、次いで、挿入は、 『償却定数』ではないであろう。だから、それは単に空間を持っていることではありません。毎回より多くのスペースを挿入する必要があります。
Nicol Bolas

@ブライアン:はい、dequeコンテナ内のアイテムの数に関してどちらの挿入も「償却定数」であるという要件については何もありません。コンテナ内のアイテムに対する操作の数に関してのみ一定です。
Nicol Bolas

「償却定数」とは、定数cが存在することを意味します。このため、N回の操作を行うと、合計所要時間は最大でもcNになります。私が言ったように、両端キューが前部のスペースを維持しない場合、前部への挿入は定数で償却できません。両端キューが前部に十分なスペースを確保している場合(あなたが言うように、一定量以上)、それを一定に償却することが可能です。ただし、どちらの場合も、要素の操作に関しては一定です。
ブライアン

1

複雑なドメインの意味をどのように解釈するかについては、少し進んでいると思います。あなたは、「線形時間」と「線形複雑さ」を区別しようとしているのですが、私にはあまり理解されていません。

前部への挿入が一定時間であることは規格によって明確であり、私たちは皆、それに同意していると思います。後者のパッセージは、その「一定の」量のオペレーションのそれぞれその下に含まれるものは、単に規格によって指定または制約されていないことを示しています。

そして、これは珍しいことではありません。どのアルゴリズムも抽象化に基づいて機能します。個々の機械語命令に分類されるアルゴリズムを記述し、アルゴリズムによって生成される機械語命令がN個しかないと言ったとしても、個々の複雑度がプロセッサ内部でどのような複雑さを持っているかを調査するつもりはありません。結果に追加します。一部の操作が量子分子レベルでより多くのことを行うことになるとは言えないため、O(n)アルゴリズムは実際にはO(N×M 3などです。このレベルの抽象化は考慮しないことにしました。そして、その複雑さがアルゴリズムの入力に依存しない限り、それはまったく問題ありません。

あなたのケースでは、移動/コピーされた内部ベクトルのサイズは実際には関係ありません。これは、両端キューが大きくなるにつれて本質的に変化しないためです。内部ベクトルのは異なりますが、ベクトルのサイズは独立したプロパティです。したがって、新しい要素を外側のベクトルに挿入することの複雑さを説明する場合は無関係です。

実際の実行時間(選択した場合、それ自体がアルゴリズムの用語で説明される可能性があります)は、コピーされた内部ベクトルの内容によって異なりますか?はい、もちろん。しかし、それは、外側のベクトル自体が大きくなるにつれて、外側のベクトルを拡張するタスクがワークロードでどのように大きくなるかとは関係ありません。

だから、ここで、標準では、それはコピーしないだろうと言っているNまたはN 2、あるいはログインNあなたが前面に別のものを入れたときに、内側のベクトルを、これらの演算の数は、両端キューが大きくなっても変化しないと言っています。また、そのルールの目的のために、内部ベクトルのコピー/移動が実際にどのようなものであるか、またはそれらがどれほど大きいかは問題ではないことも述べています。

複雑性分析はパフォーマンスに関するものではありません。複雑性分析は、パフォーマンスのスケーリング方法に関するものです。


ブートノート:本当に必要な場合は、操作の複雑さをO(1)ではなくO(A + B + C + D + E + F + ...)として説明できます。これらの内部変数は、既存の要素、またはそれらの他のいくつかの機能に影響を与えました...しかし、それは(a)役に立たない、(b)その節で定義されているルールのポイントに加えて、(c)標準以降の時間の無駄になりますこれらの既存の要素への影響はないことをすでにほとんど私達に知らせました。
翼のある小惑星
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.