掲示板ランキング  レギュラー(グァテマラ)  レギュラー(ブラジル)  レギュラー(ハワイコナ)


掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板1

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧

No.7397

非再帰版マージソート
投稿者---KUMA(2007/04/29 16:36:57)


非再帰版のマージソートプログラムを作っています。再帰版のマージソート
はとても速いのですが、非再帰版が思うように速度が出ません。
データの個数が2^Nでなくても、奇数でもソートできるようにしたいの
でこのようなプログラムにしたのですが、効率が悪いのでしょうか?

コンパイラはBorland C++ 5.8.2です。
#pragma comment(lib, "winmm.lib")を付けるとVCでもコンパイルできる
と思います。

//
// 非再帰版マージソート(qsortとの時間比較付き)
//
// 考察: Nが大きくなればなるほどqsortとの差が開くが、これはデータキャッシュ・ミスによるものと思われる。
//

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

#define N 10000

int a[N], b[N], c[N]; /* 作業領域 */

int comp(const void *a, const void *b);
void qsort_sub(unsigned t);

int main(void)
{
  int i, j, x, y, x1, y1, f, t;
  DWORD t1;
  
  t = (unsigned)time(NULL);
  srand(t);
  for (i = 0; i < N; i++)
    a[i] = rand();
  
  t1 = timeGetTime();
  do {
    /* 部分ランへの分割 */
    x = y = f = 0; x1 = y1 = -1;

    for (i = 0; i < N; i++)
      if (f == 0) {
        if (x1 <= a[i])
          b[x++] = x1 = a[i];
        else {
          f = 1;
          c[y++] = y1 = a[i];
        }
      } else /* f == 1 */ {
        if (y1 <= a[i])
          c[y++] = y1 = a[i];
        else {
          f = 0;
          b[x++] = x1 = a[i];
        }
      }

      /* 部分ランのマージ(配列の交換がなくなったら終了) */
      x1 = y1 = j = f = 0;

      for (i = 0; i < N; i++)
        if (x1 == x) {
          a[j++] = c[y1++];
          if (f == 3);
          else if (f == 1)
            f = 3;
          else
            f = 2;
        } else if (y1 == y) {
          a[j++] = b[x1++];
          if (f == 3);
          else if (f == 2)
            f = 3;
          else
            f = 1;
        } else if (b[x1] <= c[y1]) {
          a[j++] = b[x1++];
          if (f == 3);
          else if (f == 2)
            f = 3;
          else
            f = 1;
        } else { /* b[x1] > c[y1] */
          a[j++] = c[y1++];
          if (f == 3);
          else if (f == 1)
            f = 3;
          else
            f = 2;
        }
      /* ソート済みになったかチェック */
//      f = 0;
//      for (i = 0; i < N - 1; i++)
//        if (a[i] > a[i + 1]) {
//          f = 1;
//          break;
//        }
  } while (f == 3);

//  printf("a = ");
//  for (i = 0; i < N; i++)
//    printf("%d ", a[i]);
//  putchar('\n');
  
  printf("%u ms\n", timeGetTime() - t1);
  
  qsort_sub(t);
  
  return 0;
}

int comp(const void *a, const void *b)
{
  return *(int *)a - *(int *)b;
}

void qsort_sub(unsigned t)
{
  DWORD t1;
  int i;

  srand(t);
  for (i = 0; i < N; i++)
    a[i] = rand();
  
  t1 = timeGetTime();

  qsort(a, N, sizeof(int), comp);

  printf("%u ms\n", timeGetTime() - t1);
}




この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:非再帰版マージソート 7398 ぽへぇ 2007/04/29 21:20:07
<子記事> Re:非再帰版マージソート 7401 あかま 2007/04/30 19:24:51


No.7398

Re:非再帰版マージソート
投稿者---ぽへぇ(2007/04/29 21:20:07)


fって何のために使うのですか?

#手元にある本では セジウィックのアルゴリズム in C 第1巻 かなぁ。



この投稿にコメントする

削除パスワード

No.7399

Re:非再帰版マージソート
投稿者---KUMA(2007/04/29 22:41:08)


>fって何のために使うのですか?
>
>#手元にある本では セジウィックのアルゴリズム in C 第1巻 かなぁ。

レスありがとうございます。fは上のループでは昇べきの順にコピーできる
限り並べるためのフラグ、下のループでは配列の交換が起きた時はソート
されていないと判断するための物です。

下のfを取り去ってコメントアウトしてある昇べきの順かどうか調べる
ループで置き換える事もできます。

それからセジウィックの本は3巻とも持っています。その本の中では
ボトムアップのマージソートとして紹介されていますが、残念ながら
リスト構造の例しか載せてありません。

リストのやり方はわかっているので、配列の方法で効率の良いものを
知りたいのです。


この投稿にコメントする

削除パスワード

No.7401

Re:非再帰版マージソート
投稿者---あかま(2007/04/30 19:24:51)


>非再帰版のマージソートプログラムを作っています。再帰版のマージソート
>はとても速いのですが、非再帰版が思うように速度が出ません。
>データの個数が2^Nでなくても、奇数でもソートできるようにしたいの
>でこのようなプログラムにしたのですが、効率が悪いのでしょうか?

>// 考察: Nが大きくなればなるほどqsortとの差が開くが、これはデータキャッシュ・ミスによるものと思われる。
理想はO(NlogN)ですけど、
whileの中にfor文が2つあるからなんて感じで
O(2NlogN)とかになってる可能性はありませんか?
(定数は消えるのでそれでもO(NlogN)と書くのが正しいですが。)

O(2NlogN)なら
データ数4倍→実行時間16倍
データ数10倍→実行時間66倍
になるはずです。

で、私の環境で実測したところ
データ数10000→500ms
データ数40000→6500ms→実行時間13倍
データ数100000→46000ms→実行時間90倍

O(2NlogN)説はそれほど外れてもいない気がします。
同じデータサイズでクイックソートが理想値出すなら
キャッシュよりプログラム疑ったほうがいいです。経験的に。
あとコンパイラも疑いたくもなるけどまず自分のミス。

ちなみに経験的にというのは、
昔バブルソートで書かれたプログラムをクイックソートで書き直せという課題がでて
バルブソート数百秒→クイックソート1.5秒にしてやったぜ!クイック最高!
とか思ってたら「クイックならまず1秒かかりません」とか言われて
書き直したらその通りだったことが。
間違ったクイックっぽいなにかを書いてたんですね。
なんとなく今回もその臭いがします。

あぁ、なんか昔を思い出してしまった(涙)
せっかくなので私なりのマージソートおいときます。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

#define N 10000

int a[N],b[N];

int *merge_sort(void){
    int cut_size,base;
    int i,j,i_max,j_max,tp;
    
    int *data = a;
    int *tmp = b;//tmp=作業領域
    int *p;
    int k;
    for(cut_size=1;cut_size < N;){//このループがlogN
        cut_size *= 2;//分割してるサイズ
        tp=0;//tmp用インデックス
        for(base = 0;base < N;base += cut_size){//N^2に見えてただのN
            i = base;
            i_max = j = base + cut_size/2;
            j_max = base + cut_size;
            if(i_max >= N){//ここと
                while(i < N) tmp[tp++] = data[i++];
                break;
            }
            if(j_max > N) j_max = N;//ここが「データの個数が2^Nでなくても、奇数でもソート」にあたる.
            for(;;){
                if(data[i] > data[j]){
                    tmp[tp] = data[i];
                    tp++;i++;
                    if(i >= i_max){//iが終わりなら
                        while(j < j_max) tmp[tp++] = data[j++];//jを全部
                        break;
                    }
                }
                else{
                    tmp[tp] = data[j];
                    tp++;j++;
                    if(j >= j_max){
                        while(i < i_max) tmp[tp++] = data[i++];
                        break;
                    }
                }
            }
        }
        p = tmp;//データと作業領域の入れ替え
        tmp = data;
        data = p;
    }
    return data;//a[N]かb[N]かわからんが返ったほうがソート済み
}

int main(){
    int t1,t2,i;
    srand(time(NULL));
    for (i = 0; i < N; i++) a[i] = rand();
    t1 = timeGetTime();
    
    merge_sort();
    
    t2 = timeGetTime();
    printf("%u ms\n",t2-t1);
    
    return 0;
}





この投稿にコメントする

削除パスワード

No.7403

Re:非再帰版マージソート
投稿者---KUMA(2007/04/30 22:11:39)


>理想はO(NlogN)ですけど、
>whileの中にfor文が2つあるからなんて感じで
>O(2NlogN)とかになってる可能性はありませんか?
>(定数は消えるのでそれでもO(NlogN)と書くのが正しいですが。)

あかま様レスありがとうございます。
そうですか、キャッシュミスよりプログラムミスでしたか。確かに差が
開きすぎですよね。このマージソートプログラムは元々P250、§10.1、
マージソートによる外部ソート処理、「STLによるコンポーネントデザイン」、
アスキーアジソンウェスレイシリーズ、ISBN4-7561-3422-Xのプログラムを
改造してC用にしたものです。
このプログラムはシーケンシャルアクセスしかできない外部記憶装置を
配列に使うための物なので、ベストなアルゴリズムは無理なんでしょう。

ところであかま様よりいただいたプログラムは大変高速に動作しております。
N=1000000で私の環境ではマージソート310ms程度、qsort()1827msと、
標準関数のqsort()を凌ぐ速度を出しております。この場合qsort()は
比較のためにいちいち専用関数をCALLするためのオーバーヘッドが余計に
掛かっていると見た方が良いでしょう。C++のstd::sort()アルゴリズムを
使っても225ms掛かってますから、相当速いです。

今からじっくりとプログラムを研究させていただきます。本当にありがとう
御座いました。


この投稿にコメントする

削除パスワード

No.7404

Re:非再帰版マージソート
投稿者---KUMA(2007/05/01 09:47:36)


おはようございます。
VC8.0+STLport5.1.3で念のため同じテストを行ったところ(Release)

非再帰版マージソート 250ms
qsort() 390ms
std::sort 127ms

という結果になりました。qsort()は処理系で大きく変わるみたいですね。
とここまで書いたらMinGW+STLport5.1.3でもテストしたくなったので
やってみたら

非再帰版マージソート 273ms
qsort() 264ms
std::sort 128ms

という結果になりました。処理系でも大きく異なるようなのでいくつかの
処理系で試した結果も大事ですね。


この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧





掲示板提供:(有)リアル・インテグリティ