すべて、
この質問は this one の続きです。私はSTLがこの機能を欠いていると思うが、それはただの私見だ。
さて、質問に。
次のコードを検討してください。
class Foo
{
public:
Foo();
int paramA, paramB;
std::string name;
};
struct Sorter
{
bool operator()(const Foo &foo1, const Foo &foo2) const
{
switch( paramSorter )
{
case 1:
return foo1.paramA < foo2.paramA;
case 2:
return foo1.paramB < foo2.paramB;
default:
return foo1.name < foo2.name;
}
}
int paramSorter;
};
int main()
{
std::vector<Foo> foo;
Sorter sorter;
sorter.paramSorter = 0;
// fill the vector
std::sort( foo.begin(), foo.end(), sorter );
}
任意の時点で、ベクトルを並べ替えることができます。このクラスには、ソーター構造で使用されるゲッターメソッドもあります。
ベクターに新しい要素を挿入する最も効率的な方法は何ですか?
私が持っている状況は:
クラスのソートされたベクトルを使用するグリッド(スプレッドシート)があります。いつでもベクトルを並べ替えることができ、グリッドはそれに応じて並べ替えられたデータを表示します。
次に、新しい要素をベクター/グリッドに挿入する必要があります。グリッド全体を挿入、再ソート、および再表示できますが、これは特に大きなグリッドでは非常に非効率的です。
任意の助けをいただければ幸いです。
質問に対する簡単な答え:
template< typename T >
typename std::vector<T>::iterator
insert_sorted( std::vector<T> & vec, T const& item )
{
return vec.insert
(
std::upper_bound( vec.begin(), vec.end(), item ),
item
);
}
述語付きのバージョン。
template< typename T, typename Pred >
typename std::vector<T>::iterator
insert_sorted( std::vector<T> & vec, T const& item, Pred pred )
{
return vec.insert
(
std::upper_bound( vec.begin(), vec.end(), item, pred ),
item
);
}
Predは、タイプTの厳密に順序付けられた述語です。
これが機能するには、入力ベクトルがこの述部で既にソートされている必要があります。
これを行うことの複雑さは、upper_bound
検索(挿入場所の検索)ではO(log N)
ですが、挿入自体ではO(N)
までです。
より複雑にするために、重複がない場合はstd::set<T>
を使用し、重複する可能性がある場合はstd::multiset<T>
を使用できます。これらは自動的にソートされた順序を保持し、これらに対しても独自の述語を指定できます。
他にも、もっと複雑なことができるさまざまなことがあります。新しく追加されたアイテムのvector
とset
/multiset
/sorted vector
を管理し、それらが十分にあるときにそれらをマージします。コレクションを反復処理する場合は、両方のコレクションを実行する必要があります。
2番目のベクトルを使用すると、データをコンパクトに保つことができます。ここで、「新しく追加された」アイテムvector
は比較的小さいので、挿入時間はO(M)
になります。ここで、M
はこのベクトルのサイズであり、O(N)
毎回大きなベクトルに挿入します。マージはO(N+M)
になり、O(NM)
よりも優れているので、一度に1つずつ挿入するので、O(N+M) + O(M²)
になり、M
要素を挿入してからマージします。
おそらく挿入ベクトルもその容量で維持するので、要素が移動するだけで、再割り当てを行わないようになると成長します。
ベクトルを常にソートしたままにする必要がある場合は、最初に_std::set
_または_std::multiset
_を使用してもコードが単純化されないかどうかを検討できます。
ソートされたベクトルが本当に必要で、要素をすぐに挿入したいが、ソート基準が常に満たされるように強制したくない場合は、最初に std::lower_bound()
要素が対数時間で挿入されるソート範囲内の位置を見つけるには、 insert()
を使用しますvector
のメンバー関数は、その位置に要素を挿入します。
パフォーマンスが問題になる場合は、_std::list
_対_std::vector
_のベンチマークを検討してください。小さなアイテムの場合、_std::vector
_はキャッシュヒット率が高いために高速であることが知られていますが、insert()
操作自体はリスト上で計算が高速です(要素を移動する必要はありません)。
ご注意ください。必要に応じてupper_bound
も使用できます。 upper_bound
は、他と同等の新しいエントリがシーケンスのendに表示されることを保証し、lower_bound
は、他と同等の新しいエントリがに表示されることを保証します先頭シーケンスの。特定の実装に役立つ場合があります(詳細はすべてではなく「位置」を共有できるクラスかもしれません!)
Bothは、要素の<
結果に従ってベクトルがソートされたままであることを保証しますが、lower_bound
に挿入すると、より多くの要素を移動することになります。
例:
insert 7 @ lower_bound of { 5, 7, 7, 9 } => { 5, *7*, 7, 7, 9 }
insert 7 @ upper_bound of { 5, 7, 7, 9 } => { 5, 7, 7, *7*, 9 }
挿入およびソートの代わりに。検索してから挿入する必要があります
ベクトルをソートしたままにします。 (一度ソート)。挿入する必要があるとき
挿入する要素と比較して大きい最初の要素を見つけます。
その位置の直前に挿入します。
これにより、ベクターはソートされたままになります。
次に例を示します。
start {} empty vector
insert 1 -> find first greater returns end() = 1 -> insert at 1 -> {1}
insert 5 -> find first greater returns end() = 2 -> insert at 2 -> {1,5}
insert 3 -> find first greater returns 2 -> insert at 2 -> {1,3,5}
insert 4 -> find first greater returns 3 -> insert at 3 -> {1,3,4,5}
並べ替え順序を切り替える場合、複数のインデックスデータ構造を使用できます。各インデックス構造は、並べ替えられた順序で保持されます(おそらく、並べ替えキーをvector-indices、またはstdにマッピングするstd :: mapなどの何らかのバランスツリー::オブジェクトへのポインタを格納するように設定します-ただし、異なる比較関数を使用します)。
これを行うライブラリを次に示します。 http://www.boost.org/doc/libs/1_53_0/libs/multi_index/doc/index.html
すべての変更(新しい要素の挿入またはキーの更新)ごとに、すべてのインデックスデータ構造を更新するか、無効としてフラグを立てる必要があります。
これは、データ構造の「多すぎる」ソート順や「多すぎる」更新がない場合に機能します。それ以外の場合-不運、順序を変更するたびに再ソートする必要があります。
言い換えると、(ルックアップ操作を高速化するために)必要なインデックスが多いほど、更新操作に必要な時間が長くなります。もちろん、すべてのインデックスにはメモリが必要です。
インデックスの数を小さく保つために、いくつかのフィールドのインデックスを組み合わせて、複数のフィールドでより複雑なソート順をサポートするクエリエンジンを使用できます。 SQLクエリオプティマイザーのような。しかし、それはやり過ぎかもしれません...
例:2つのフィールドaとbがある場合、4つのソート順をサポートできます。
2つのインデックス(3および4)。フィールドが増えると、ソート順序の可能な組み合わせが大きく、速くなります。ただし、「ほぼ必要に応じて」並べ替えるインデックスを引き続き使用し、クエリ中に、必要に応じてそのインデックスでは捕捉できなかった残りのフィールドを並べ替えることができます。データ全体をソートして出力する場合、これはあまり役に立ちません。ただし、一部の要素のみを検索する場合は、最初の「絞り込み」が大いに役立ちます。