web-dev-qa-db-ja.com

list :: size()は本当にO(n)ですか?

最近、std::list::size()の線形複雑性に言及している人がいることに気づきました。
somesources によると、これは実際には実装に依存しているため、標準では複雑さを説明する必要がありません。
コメント このブログエントリ内 のコメント:

実際には、使用しているSTLによって異なります。 Microsoft Visual Studio V6は、size()を{return(_Size);として実装します。一方、gcc(少なくともバージョン3.3.2および4.1.0では)は、{return std :: distance(begin()、end());として実行します。最初の速度は一定、2番目の速度はo(N)速度

  1. したがって、VC++群衆の場合、DinkumwareはおそらくVC6以降、その事実を変更していないため、size()は一定の複雑さを持っていると私は推測しています。私はそこにいますか?
  2. 現在gccにはどのように表示されますか?それが本当にO(n)である場合、開発者はなぜそうすることを選択したのですか?
61
foraidt

C++ 11以前の回答

規格ではlist :: size()の複雑さは何である必要があるかを明記していないのは正しいですが、「一定の複雑さを持たせる」ことをお勧めします(表65の注A)。

これはHoward Hinnantによる興味深い記事です これは、一部の人々がlist :: size()がO(N)の複雑さを持つべきだと考える理由を説明します(基本的にはO(1) list :: size()はlist :: splice()にO(N)複雑さ)を与え、O(1) list :: size()を使用する理由(著者の意見では)良い考えです:

論文の要点は次のとおりです。

  • 内部カウントを維持してlist::size()をO(1)にできる状況がいくつかあり、スプライス操作が線形になる
  • 誰かがO(N) size()を呼び出すために発生する可能性のある悪影響に気づかない可能性のある状況がさらに多くあるでしょう(たとえば、list::size()がロックされている間に呼び出されます)。
  • size()がO(N)であることを許可する代わりに、「最小の驚き」のために、規格はsize()を実装するすべてのコンテナにO(1)ファッション。コンテナがこれを実行できない場合は、size()をまったく実装しないでください。この場合、コンテナーのユーザーはsize()が利用できないことを認識し、コンテナー内の要素の数を取得したい、または取得する必要がある場合は、container::distance( begin(), end())を使用してその値を取得しますが、O(N)操作であることを完全に認識します。

私は彼の推論のほとんどに同意する傾向があると思います。ただし、splice()オーバーロードへの彼の提案された追加は好きではありません。正しい動作を得るには、distance( first, last)と等しいnを渡す必要があることは、バグの診断が難しいレシピのようです。

どんな変更も既存のコードに大きな影響を与えるので、私は今後何をすべきか、または何ができるかわかりません。しかし現状では、既存のコードはすでに影響を受けていると思います-明確に定義されているはずの何かについて、実装ごとに動作がかなり異なる可能性があります。たぶん、サイズを「キャッシュ」にして、既知/不明としてマークすることについての一人ひとりのコメントはうまくいくかもしれません-償却されますO(1)の動作-O(N)の動作を得る唯一の時いくつかのsplice()操作によってリストが変更されたときです。これの良い点は、標準に変更を加えなくても、今日実装者がそれを実行できることです(私が何かを見落としている場合を除きます)。

私の知る限り、C++ 0xはこの領域で何も変更していません。

52
Michael Burr

C++ 11では、any標準コンテナの場合、.size()操作は「一定の」複雑さ(O(1))で完了する必要があります。 (表96-コンテナー要件)。以前はC++ 03で.size()shouldは一定の複雑さを持っていますが、必須ではありません( Is std :: string size()a O(1)操作? )。

標準の変更は n2923:size()の複雑さの指定(リビジョン1) によって導入されます。

ただし、libstdc ++での.size()の実装では、4.8 =までのgccでO(N)アルゴリズムが使用されます。

_  /**  Returns the number of elements in the %list.  */
  size_type
  size() const _GLIBCXX_NOEXCEPT
  { return std::distance(begin(), end()); }
_

なぜそれがこのように保たれるのかについての詳細は c ++ 11でstd :: listが大きい理由 も参照してください。

更新std::list::size()適切にO(1) gccを使用する場合5.0C++ 11モード(またはそれ以上)。


ちなみに、libc ++の.size()は正しくO(1)です。

__LIBCPP_INLINE_VISIBILITY
size_type size() const _NOEXCEPT     {return base::__sz();}

...

__compressed_pair<size_type, __node_allocator> __size_alloc_;

_LIBCPP_INLINE_VISIBILITY
const size_type& __sz() const _NOEXCEPT
    {return __size_alloc_.first();}
_
72
kennytm

以前にgcc 3.4のlist :: sizeを調べたことがあるので、次のように言うことができます。

  1. std :: distance(head、tail)を使用します
  2. std :: distanceには2つの実装があります。RandomAccessIteratorを満たす型の場合は「tail-head」を使用し、InputIteratorのみを満足する型の場合はO(n)に依存するアルゴリズムを使用しますiterator ++ "、指定された末尾に到達するまで数えます。
  3. std :: listはRandomAccessIteratorを満足しないため、サイズはO(n)です。

「理由」については、std :: listがシーケンシャルアクセスを必要とする問題に適していると言えます。サイズをクラス変数として保存すると、挿入、削除などのたびにオーバーヘッドが発生しますが、その無駄はSTLの意図からして大きな違いです。本当に一定時間のsize()が必要な場合は、std :: dequeを使用してください。

14
introp

サイズがO(N)であることが許可されている唯一の理由として、私はスプライスがO(N))であるという問題を個人的には見ていません。使用しないは重要なC++のモットーです。この場合、リストのサイズを維持するかどうかにかかわらず、リストのサイズを維持するには、挿入/消去ごとに追加の増減が必要になります。これは小さな固定オーバーヘッドですが、考慮することはまだ重要です。

リストのサイズを確認する必要はほとんどありません。合計サイズを気にせずに最初から最後まで繰り返すのは、はるかに一般的です。

12
Greg Rogers

sourcearchive )に移動します。 SGIのSTLページは、線形の複雑さを持つことが許可されていると述べています。彼らが従った設計ガイドラインは、リストの実装をできる限り一般的なものにすること、したがってリストをより柔軟に使用できるようにすることだったと思います。

4
Yuval F

このバグレポート: [C++ 0x] std :: list :: size complex は、GCC 4.xの実装が線形時間であるという事実と、どのように一定時間への移行が行われるかという事実を非常に詳細にキャプチャします。 C++ 11の場合、ABIの互換性の問題により、リリースが遅くなりました(5.0で利用可能)。

GCC 4.9シリーズのマンページには、次の免責事項がまだ含まれています。

C++ 11のサポートはまだ実験段階であり、将来のリリースでは互換性のない方法で変更される可能性があります。


同じバグレポートがここで参照されています: C++ 11ではstd :: list :: sizeは一定の複雑さを持っている必要がありますか?

1
nobar

リストを正しく使用している場合は、違いに気付かないでしょう。

リストは、コピー後に有効なポインターを保持したいデータについて、コピーせずに再配置したいビッグデータ構造に適しています。

最初のケースでは違いはありません。2番目のケースでは、古い(小さい)size()実装を使用します。

とにかくstdは、生の速度よりも正確さと標準の動作と「ユーザーフレンドリー」についてです。

0
Luke Givens