HIRASE CONNECTION WK

programming collection

目次

Blog 利用状況

ニュース

あわせて読みたいブログパーツ

書庫

日記カテゴリ

Link Collection

[C++] ハトの巣ソート

ハトの巣ソート(ピジョンソート、Pigeon sort)。

いまいち理解できていないので、Wikipedia様に丸投げ→→「Pigeonhole sort」。( 18:06, 19 March 2008版には擬似コードもあり。)

メモリは食うけど、ソートは馬鹿早い。同じ要素の並びは変わらない安定ソート。

追記@2008-05-12T23:39+09:00:新しいバージョンをアップ。

/* データはユニークである必要があります。 */
template<typename TElement, typename TSize>
void PigeonholeSort(TElement * data, const TSize size)
{
    TElement min = data[0];
    TElement max = min;
    for (TSize i = 1; i < size; ++i) {
        min = data[i] < min ? data[i] : min;
        max = data[i] > max ? data[i] : max;
    }

    TSize range = max - min + 1;
    TElement * pigeonholes = new TElement[range];
    for (TSize i = 0; i < range; ++i)
        pigeonholes[i] = 0;	/* memsetで十分。 */

    for (TSize i = 0; i < size; ++i)
        pigeonholes[data[i]-min] = data[i];

    TSize dataPos = 0;
    for (TSize i = 0; i < range; ++i)
        if (pigeonholes[i] != 0)
            data[dataPos++] = i + min;

    delete[] pigeonholes;
}

追記@2008-05-08T17:19+09:00:下記のコードはちょっと間違えています。そのうち修正しますので、くれぐれも参考にしないよう、お願いします。

#include <map>
 
template<typename TElement, typename TSize>
void PigeonholeSort(TElement * data, const TSize size)
{
    typedef std::map<TElement, std::vector<TElement> > TMap;
    TMap pigeonholes;
    
    for (TSize i = 0; i < size; ++i)
        pigeonholes[data[i]].push_back(data[i]);
    
    TSize dataPos = 0;
    const TMap::const_iterator end = pigeonholes.end();
    for (TMap::iterator it = pigeonholes.begin(); it != end; ++it)
    {
        const TMap::value_type & pigeonhole = *it;
        const TSize size =  pigeonhole.second.size();
        for (TSize i = 0; i < size; ++i)
        {
            data[dataPos++] = pigeonhole.first;
        }
    }
}

投稿日時 : 2008年5月8日 1:37

コメントを追加

# re: [C++] ハトの巣ソート 2008/05/08 15:14 出水

これは強いて言えばヒープソートであり、ハトの巣ソートではありません

あと、こうですね
data[dataPos++] = pigeonhole.first;

data[dataPos++] = pigeonhole.second[i];

# re: [C++] ハトの巣ソート 2008/05/08 17:18 T.Hirase

TO: 出水さま。
確かに・・・。
カウンティングソートの一種なので、10行目の
# for (TSize i = 0; i < size; ++i)
# pigeonholes[data[i]].push_back(data[i]);
は、データを入れずに、カウントするのが本当ですね。

>data[dataPos++] = pigeonhole.first;
>↓
>data[dataPos++] = pigeonhole.second[i];
これは、firstで問題ないかと(カウントする方法に変えれば、確実にfirstで)。

まだ何か勘違いしてるかもしれないので、
コードの修正は改めて行います。

# re: [C++] ハトの巣ソート 2008/05/08 18:21 出水

カウントはあまり関係ありません(Wikipedia版ではカウントしてないし)

>pigeonholes[data[i]]
ハトの巣ソートの肝はここがO(1)であることです
mapを使ってしまうとO(logN)になるので、ここは配列(vectorとか)である必要があります
ハトの巣ソートはクイックソートより速いのに使われない理由を理解しておくべきです

修正点は、A=Bだが、A≡Bでない、という関係を想定しています
そもそも、安定である/ないを論議するには上記の関係が前提となります

# re: [C++] ハトの巣ソート 2008/05/12 23:38 T.Hirase

返事、遅くなりました。

ちょっとわからなくなってしまったのですが、
Wikipediaに載ってる方法だと、キーが重複した際の実装方法が書いてないように思います。一方、下記のページには、「ハトの巣ソートのキー値はユニーク」と書いてあります。
http://www.experts-exchange.com/Programming/Languages/CPP/Q_23286666.html

どちらが正しいのかは私には判断できないですが、ユニークでないキーが来る可能性があるなら、pigeonholesの各穴をキューにしてデータを貯めなければいけない気がします。
また、キューにデータを放り込む際にメモリコピーが発生する可能性も否定できないので、「ハトの巣ソートがクイックソートより使われない理由」というのは、以下のような感じでしょうか。
(a) キー値がユニークであることを保障できるケースが少ない。
(b) キーの重複がどれだけ起きて、メモリコピーがどれだけ発生するか見積もれない。
(c) ソートに必要となるメモリサイズを見積もれない。


※新たに作ったソースコードを追記しておきます。
(前のコードは、いずれ消します)

# re: [C++] ハトの巣ソート 2008/05/14 2:03 出水

原始的なハトの巣ソートは整数の配列しか並び替えられません
floatやstringは並び替えられないのです

新しいプログラムの18行目の data[i]-min がとても重要です
"TElement 同士は引き算が行え、結果は整数となる"
これが成り立つことがハトの巣ソートの使える条件です
引き算が出来ないstringはもちろん、
出来ても整数にならないfloatが使えない理由がこれです

(c)で出てますが、配列のサイズが現実的に小さい、というのも必要な条件です
人を年齢順にソートできても、年収順にソートするのは厳しいです

なお、そこのサイトにある、ユニークである必要があるという説明は間違ってます
そもそも、そこのサンプルプログラムは乱数生成こそユニークですが、
ソートの部分はユニークでなくてもちゃんと並び替えられると思います(実行してないから断言は出来ませんが)

なお、最初のプログラムも7行目が
std::vector<TElement> *pigeonholes = new std::vector<TElement> (MaxData);
であれば、ハトの巣ソートになります

mapを使うことで、mapのアルゴリズムを利用したソートになってしまい
mapのアルゴリズムがヒープソートとほぼ同じなのでヒープソートと表現したのです
実際、multimapを使えばもっとすっきり書けますしね

# 【20080920東京勉強会#24】準備エントリ 2008/08/12 16:06 はつね

【20080920東京勉強会#24】準備エントリ

タイトル  
名前  
URL
コメント