C言語関係掲示板

過去ログ

No804 リストのクイックソート

[戻る] [ホームページ]
No.10072

クイックソートの2回目以降
投稿者---内膜(2003/10/26 23:15:06)


クイックソートをしたいんですけど最初の軸で
先頭からと末尾から比較していくと2つに分かれますよね?
その2つをこんどはどうやって比較して分けていけばいいかわかりません。
ちょっと自分で言ってることもよくわからないんですがお願いします。

とりあえず考えたんですがこれ以上進まなくて。
考えたソースは下のやつです。
headとtailは双方向リストの先頭と末尾です。
pvは軸です。あと軸を調べる関数はchar *MyPivot()ということしてます。

void Quicksort(CELL **head,CELL **tail,char *pv){
  int x;
  CELL *p,*k;
    p = *head;
    k = *tail;
  for(;;){
    
    x = strcmp(pv,p->id_number);
    if(x < 0){
      for(;;){
        x = strcmp(pv,k->id_number);
        if(x > 0){
          break;
        }
        else
          k = k->prev;        
      }
    }
    else
      p = p->next;
  }
}


No.10074

Re:クイックソートの2回目以降
投稿者---たか(2003/10/26 23:46:41)


>先頭からと末尾から比較していくと2つに分かれますよね?
>その2つをこんどはどうやって比較して分けていけばいいかわかりません。

夜も遅くなりましたので今日はソースは書く時間がありませんが、要点
だけ書いておきます。
軸を残して、前後二つに分かれた部分ソート部(マージソートではランと
いいますが)に対してそれぞれ再帰的に自分自身を適用します。そして、
最後の要素が1つだけあるいは軸以外に要素がなくなった時点で戻って
こればいいと思います。

それにしてもリスト構造ですね。私は以前リスト構造に対してQuickSort
を適用するプログラムを書きましたが、配列と違い添え字でアクセスでき
ないのであまりQuickSort向きではありません。幸いな事に
BiDirectionalIteratorの特性を持てばQuickSortは実現できそうです。
軸を指すポインタの他に前と後を指す2つのポインタ、つまり3つの
ポインタが必要です(間違っていたらすみません)。但し中間値は要素の
数が簡単にはわかりませんので先頭の値を使う事になります。やっぱり
あまり向いてないですね。

リスト構造に最も自然なソートは、マージソートでしょう。どちらの
ソートもコストはそんなに変わりません。

No.10075

Re:クイックソートの2回目以降
投稿者---内膜(2003/10/27 00:06:18)


ポインタを使ってクイックソートをせろ、ということは
リストを使えということにはならないのですか?
データも構造体ですし。

No.10076

Re:クイックソートの2回目以降
投稿者---RAPT(2003/10/27 00:52:12)


>ポインタを使ってクイックソートをせろ、ということは
>リストを使えということにはならないのですか?
>データも構造体ですし。
必ずしもそうとは限らない。
配列とポインタとの関係とか。

No.10078

Re:クイックソートの2回目以降
投稿者---内膜(2003/10/27 01:29:04)


なるほど。
たかさん、RAPTさんありがとうございました。

No.10083

Re:クイックソートの2回目以降
投稿者---ceybord(2003/10/27 10:05:23)


少し疑問に思うことがあるのですが、
スタックがオーバーしたときの処理を考える必要はないのですか?
(クイックソートで、メモリオーバー以前にスタックオーバーになるというのはかなり特殊ですが。)

No.10091

Re:クイックソートの2回目以降
投稿者---たか(2003/10/27 16:19:34)


>少し疑問に思うことがあるのですが、
>スタックがオーバーしたときの処理を考える必要はないのですか?
>(クイックソートで、メモリオーバー以前にスタックオーバーになるというのはかなり特殊ですが。)

そのために、非再帰のクイックソートというアルゴリズムもあります。但
し再帰版より長くてゴチャゴチャしますが。

それと、リストを使ってもクイックソートは出来ます。その事は昨日の晩
にも書いておきましたね。

今日はちょっと忙しいのでこんな時間にレスを返させていただきました。
後から時間があったらリスト版のクイックソートのプログラム例を載せ
ます。

No.10093

Re:クイックソートの2回目以降
投稿者---内膜(2003/10/27 16:42:49)


>そのために、非再帰のクイックソートというアルゴリズムもあります。但
>し再帰版より長くてゴチャゴチャしますが。
これはやはり再帰を使ったものより時間的な効率はいいのですか?

あと軸のとりかたで、一番左の2つの大きいほうをとるやり方や
中央の値をとるやり方があって、比較するのが数値の場合は
平均をとったりできますが、比較するのが文字列の場合平均はとれるのでしょうか?
平均をとるのに時間がかかったりして、効率は悪くならないのでしょうか?(文字列の場合)

No.10106

Re:クイックソートの2回目以降
投稿者---たか(2003/10/27 23:52:46)


>これはやはり再帰を使ったものより時間的な効率はいいのですか?
>
>あと軸のとりかたで、一番左の2つの大きいほうをとるやり方や
>中央の値をとるやり方があって、比較するのが数値の場合は
>平均をとったりできますが、比較するのが文字列の場合平均はとれるのでしょうか?
>平均をとるのに時間がかかったりして、効率は悪くならないのでしょうか?(文字列の場合)

申し訳ないです。帰宅したらこんな時間に。平均値は残っているすべての
部分ソート集合の平均を取ったりは普通はしません。3つのメジアンと
いい、任意の3要素の中央値を軸にする事はよくありますが、リスト構造
では全要素に渡る走査が発生しますので大抵はどちらかの端から3つを
取って3つとします。

ただしこんな事をするのもクイックソートの最悪のケースO(N^2)を防ぐ
ためであって、本質的には必要ありません。特に課題であればそういう
部分は削ってシンプルな形で出せばいいのです。

No.10108

Re:クイックソートの2回目以降
投稿者---たか(2003/10/27 23:56:27)


非再帰版のクイックソートは途中で他のソート法に切り替えるのが普通
で、要素数が少ない時効率的な挿入ソートを使うのが普通です。そのため
ステップ数が再帰版に比べてどうしても増えます。

他のソート法に切り替えなければステップ数はさほど変わりません。

No.10120

Re:クイックソートの2回目以降
投稿者---たか(2003/10/28 12:18:18)


一応再帰版のクイックソートプログラムが出来ました。

内容はバカ正直にリストの先頭セルをピボットとし、それより小さな文字
列idと大きな文字列idを持つ3つのリストに分け、小さな文字列のリストと
大きな文字列のリストにそれぞれ自分自身を適用し、最後に3つをくっつけ
てソート済みリストとしています。

しかし最近はstd::listばかり使っているせいか、append()やpush_front()
で思わぬバグを出していました。std::listならstd::list::sort()が使え
るし、事実上ポインタの付け替えといった煩雑な操作は一切不要です。

特に今回は大きな改良点を残しています。折角BiDirectionalIteratorなの
にend()を使わずbegin()のみを使っているためこのプログラムはForwardI
teratorとしてしか動作していません。もちろんそのため単方向リストにも
適用できるというメリットは残しているわけですが。

取り敢えず完全動作しますのでアップしておきます。C++でstd::listコン
テナを使わずとも双方向リストクラスを作ればもっとスマートに書けます
よ。


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

#define N 20

typedef struct cell {
  struct cell *prev;
  struct cell *next;
  char *id;
} CELL;

CELL *push_front(CELL *x, CELL *y)
{
  if (x != NULL) {
    x->prev = y;
    y->next = x;
  }
  return y;
}

CELL *append(CELL *x, CELL *y)
{
  CELL *p = x;
  /* x を全長に渡って走査するので、改良の余地あり。出来ればxのtailを覚えておければ。 */

  if (y == NULL) return x;
  if (p == NULL) return y;
  while (p->next != NULL) p = p->next;
  p->next = y;
  y->prev = p;
  
  return x;
}

CELL *quicksort(CELL *x)
{
  CELL *c, *i, *j, *k, *t;
  
  /* *xが空か既に単一セルなら戻る */
  if (x == NULL || x->next == NULL) return x;
  else c = x->next;
  
  /* x を単一セルとして切り落とす(先頭のセルをピボットとする) */
  x->prev = x->next = NULL;
  c->prev = NULL;
  k = c;

  /* xより小さいidを持つセルをi, 大きいidを持つセルをjに分ける */
  i = j = NULL;
  while (c != NULL) {
    /* c を単一セルとして切り落とす */
    if (c->next == NULL) 
      t = NULL;
    else {
      t = c->next;
      c->next->prev = NULL;
      c->next = NULL;
    }
    if (strcmp(c->id, x->id) < 0) i = push_front(i, c);
    else if (strcmp(c->id, x->id)) j = push_front(j, c);
    else push_front(x, c);
    c = t;
  }
  /* i, j を更にquicksortする */
  i = quicksort(i); j = quicksort(j);
  /* i, x, j の順につなげる */
  i = append(i, x);
  i = append(i, j);
  
  return i;
}

int main(void)
{
  CELL cl[N], *p;
  int i, j;
  char s[11];
  
  srand((unsigned)time(NULL));
  
  /* リストの初期化を楽するため配列を使っている */
  for (i = 0; i < N; i++) {
    for (j = 0; j < 10; j++) s[j] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[rand() % 36];
    s[10] = '\0';
    cl[i].id = strdup(s);
    if (i == 0) {
      cl[i].prev = NULL;
      cl[i].next = &cl[i + 1];
      cl[i + 1].prev = &cl[i];
    } else if (i == N - 1) {
      cl[i].prev = &cl[i - 1];
      cl[i].next = NULL;
    } else {
      cl[i].next = &cl[i + 1];
      cl[i].prev = &cl[i - 1];
    }
  }

  printf("before quick sort: ");
  for (p = &cl[0]; p != NULL; p = p->next)
    printf("%s ", p->id);
  putchar('\n');
    
  p = quicksort(&cl[0]);
    
  printf("\nafter quick sort: ");
  for (; p != NULL; p = p->next)
    printf("%s ", p->id);
  putchar('\n');

  for (i = 0; i < N; i++) free(cl[i].id);
  
  return 0;
}


No.10100

Re:クイックソートの2回目以降
投稿者---かずま(2003/10/27 21:43:50)


> headとtailは双方向リストの先頭と末尾です。

リストのクイックソートと聞いて、面白そうなので、書いてみました。
でも、単方向リストなので参考にならないかもしれません。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node {
    char *data;
    struct Node *next;
} Node;

Node *quick_sort(Node *p)
{
    Node *v = p, *a = p, *b = NULL, *next;

    if (p == NULL || p->next == NULL) return p;
    next = p->next;
    a->next = NULL;
    while (p = next) {
        next = p->next;
        if (strcmp(p->data, v->data) <= 0)
            p->next = a, a = p;
        else 
            p->next = b, b = p;
    }
    a = quick_sort(a);
    v->next = quick_sort(b);
    return a;
}

Node *create_node(Node *lp, const char *data)
{
    Node *p = malloc(sizeof(Node));
    if (p == NULL || (p->data = strdup(data)) == NULL)
        puts("out of memory"), exit(1);
    p->next = lp;
    return p;
}

void print_list(const Node *p)
{
    for (; p; p = p->next) puts(p->data);
}

int main(void)
{
    char buf[256];
    Node *root = NULL;

    while (scanf("%s", buf) == 1 && buf[0] != '.')
        root = create_node(root, buf);
    root = quick_sort(root);
    puts("---");
    print_list(root);
    return 0;
}


No.10102

Re:クイックソートの2回目以降
投稿者---かずま(2003/10/27 22:01:00)


バグがありました。次のように訂正します。
Node *v = p, *a = p, *b = NULL, *next;  を
Node *v = p, *a = NULL, *b = NULL, *next; に、

a->next = NULL;  を  v->next = NULL; に変更し、

a = quick_sort(a); の後に次の2行を追加。

      for (p = a; p->next; p = p->next) ;
      p->next = v;


No.10103

Re:クイックソートの2回目以降
投稿者---かずま(2003/10/27 22:08:08)


やっぱりまだバグがありました。
quick_sort() を次のように修正します。
Node *quick_sort(Node *p)
{
    Node *v = p, *a = NULL, *b = NULL, *next;

    if (p == NULL || p->next == NULL) return p;
    next = p->next;
    v->next = NULL;
    while (p = next) {
        next = p->next;
        if (strcmp(p->data, v->data) <= 0)
            p->next = a, a = p;
        else 
            p->next = b, b = p;
    }
    if (a) {
        a = quick_sort(a);
        for (p = a; p->next; p = p->next) ;
        p->next = v;
    } else
        a = v;
    v->next = quick_sort(b);
    return a;
}


No.10111

Re:2次元配列のrealloc
投稿者---mtk(2003/10/28 00:22:27)


リストのソートですか。実は学校の課題で似たようなものをやった時に
良いアイディアがあったので、ほとんどそのまま書いてみました。

このプログラムでは単方向ですが、
意味が分かればそのまま双方向リストに応用できると思います。
# うしろにもダミーを作るとか(?)良く分かりませんが頑張ってください。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node {
    char *data;
    struct Node *next;
} Node;

int compare(const void *a, const void *b)
{
    return strcmp((**(Node**)a).data, (**(Node**)b).data);
}

int main(void)
{
    Node dummy = {0}, *p = &dummy;
    char buf[256];
    int i, cnt;

    for (cnt = 0; scanf("%s", buf) == 1; cnt++) {
        p = p->next = malloc(sizeof(Node));
        if (p == NULL || (p->data = strdup(buf)) == NULL)
            puts("out of memory"), exit(1);
    }
{
    Node *w[cnt];
    for (i = 0, p = dummy.next; p; p = p->next)
        w[i++] = p;
    qsort(w, cnt, sizeof(Node*), compare);
    for (p = &dummy, i = 0; i < cnt; i++)
        p = p->next = w[i];
}
    for (p = dummy.next; p; p = p->next)
        puts(p->data);
    return 0;
}



No.10112

Re:クイックソートの2回目以降
投稿者---mtk(2003/10/28 00:26:52)


「このコメントに投稿する」を押して投稿する間に誰かが投稿したら
題名が変わるのかなぁ?(書くの遅いから。。。しかも直接書いてるし)

No.10125

Re:Re:クイックソートの2回目以降
投稿者---ceybord(2003/10/28 17:49:47)


>「このコメントに投稿する」を押して投稿する間に誰かが投稿したら
>題名が変わるのかなぁ?(書くの遅いから。。。しかも直接書いてるし)

きちんとした掲示板ならこんなことはないはずですが。
管理者がしっかりしていないと予期しない不正アクセスが発生するものですね。
まぁ、私もネットワーク処理はあまり詳しくないのです。
掲示板のアクセス管理にも、割り込み処理というのを使うのでしょうか?

No.10115

Re:クイックソートの2回目以降
投稿者---mtk(2003/10/28 09:37:16)


そのプログラムは偶然に動いていたようです。

1. 最後の要素の next がたまたま NULL だった。
2. ソート後の最後の要素がたまたま初めから最後にあった。
3. 1以上のデータでテストした。(これは良く分かりませんが)

ちなみにテストデータは C のソースコードでやったので、最後の要素は "}" なので
かなりの偶然で動いていたようです。

追加した所に空のコメントを書いておきました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node {
    char *data;
    struct Node *next;
} Node;

int compare(const void *a, const void *b)
{
    return strcmp((**(Node**)a).data, (**(Node**)b).data);
}

int main(void)
{
    Node dummy = {0}, *p = &dummy;
    char buf[256];
    int i, cnt;

    for (cnt = 0; scanf("%s", buf) == 1; cnt++) {
        p = p->next = malloc(sizeof(Node));
        if (p == NULL || (p->data = strdup(buf)) == NULL)
            puts("out of memory"), exit(1);
    }
    p->next = NULL;  /**/
    if (cnt > 0){  /**/
        Node *w[cnt];
        for (i = 0, p = dummy.next; p; p = p->next)
            w[i++] = p;
        qsort(w, cnt, sizeof(Node*), compare);
        for (p = &dummy, i = 0; i < cnt; i++)
            p = p->next = w[i];
        p->next = NULL;  /**/
        for (p = dummy.next; p; p = p->next)
            puts(p->data);
    }
    return 0;
}



No.10117

Re:クイックソートの2回目以降
投稿者---かずま(2003/10/28 11:22:55)


>        Node *w[cnt];
これがエラーにならないということは、gcc あるいは C99 の処理系を
お使いですね。

それから、qsort() を使うということは配列のソートを行っているということで、
その配列の要素がポインタであったり構造体であったとしても、このスレッドの
話題であるリストのソートとはちょっと話が違うのでは、という気がします。