C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[14]: 再帰をループに変換


(過去ログ 50 を表示中)

[トピック内 24 記事 (1 - 20 表示)]  << 0 | 1 >>

■27535 / inTopicNo.1)  再帰をループに変換
  
□投稿者/ C学習中 (3回)-(2008/11/09(Sun) 10:39:25)

分類:[C/C++] 

2008/11/09(Sun) 11:26:05 編集(投稿者)
Jitta on the wayさんのご指摘により
char を int に書き換えました。

お世話様です。
C言語で書いたクイックソートです
再帰で書いていますがループに変換した
コードはどうなるのでしょうか?

一応参考として知りたい程度です。
宜しくお願いします。


//
//	プログラム名
//	作成者
//	作成日
//
//	動作概要 クイックソート
//
#include <stdio.h>
#include "myutil.h"		//SWAPを使用するために参照

int  data[] = { 3,9,-8,5,9,1,-2,-5,13,0};


	int  n = sizeof(data)/sizeof(int);
	int  i;



void Quicksort(int *v,int left,int right)
{
	int i,last;
	
	if(left>=right)
		return;
		
	last=left;
	
	for(i=left+1;i <= right;i++)
	{
		if(v[i] <= v[left])
		{
			last++;
			SWAP(int,v[last],v[i]);
		}
	}	
		SWAP(int,v[left],v[last]);
		Quicksort(v ,left,last-1);
		Quicksort(v,last+1,right); 
}




int main()
{

	for(i=0;i<n;i++)
		printf("%d ",data[i]);  //元の配列の確認
	printf("\n\n");

	// ポインタで記述
	int *p =data;
	
	for(i=0;i<n;i++)
		printf("%d  ",*(p+i));  //元の配列の確認

	printf("\n\n");


	//クイックソートして表示
	Quicksort(data,0,n-1);

	for(i=0;i<n;i++)
		printf("%d ",data[i]);  //ソート後の結果の確認


	return 0;
}

引用返信 編集キー/
■27538 / inTopicNo.2)  Re[1]: 再帰をループに変換
□投稿者/ Jitta on the way (217回)-(2008/11/09(Sun) 10:59:15)
No27535 (C学習中 さん) に返信

ごめん
その前に、適切な型を用いることを覚えましょう。
なんぼなんでも、
> char n = sizeof(data)/sizeof(char);
これはないわ。

data の幅が127を越えたらどうするね?
引用返信 編集キー/
■27539 / inTopicNo.3)  Re[2]: 再帰をループに変換
□投稿者/ C学習中 (4回)-(2008/11/09(Sun) 11:29:35)
No27538 (Jitta on the way さん) に返信
> ■No27535 (C学習中 さん) に返信
>
> ごめん
> その前に、適切な型を用いることを覚えましょう。
> なんぼなんでも、
>>char n = sizeof(data)/sizeof(char);
> これはないわ。
>
> data の幅が127を越えたらどうするね?


失礼しました。
int に変更して書き直しましたのでよろしくお願いします。
引用返信 編集キー/
■27541 / inTopicNo.4)  Re[3]: 再帰をループに変換
□投稿者/ 出水 (93回)-(2008/11/09(Sun) 12:23:41)
期待するコードにはならないと思う

ニュアンスだけ取り出すとこんなコード

queue.push(pair(first, last));
while(!queue.empty()){
tmp = queue.pop()
Quicksort(v, tmp.first, tmp.last);
}

で、クイックソートの内部で、再起をする代わりにキューに追加してやればOK
要するに、再帰をしていた部分をキューに積んでやって順次処理をしているだけ

http://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%BC%E3%83%88
ここのクイックソートのメモリ使用量がO(log n)になっている
これがO(1)になっていれば、ループ展開することできれいなコードになるんだけど、
O(log n)ってことは、再帰と同じ分のメモリ消費をしてしまうことを意味している
引用返信 編集キー/
■27602 / inTopicNo.5)  Re[4]: 再帰をループに変換
□投稿者/ R・田中一郎 (2回)-(2008/11/11(Tue) 10:58:07)
R・田中一郎 さんの Web サイト
再帰を置き換えるなら、キューじゃなくてスタックじゃないかしら?

再帰処理するミニマムコードを書く。
スタックの深さと、ローカル変数を自分で管理すべく、スタックなコレクションなり、配列を操作する。

って感じになるのかな?

引用返信 編集キー/
■27603 / inTopicNo.6)  Re[5]: 再帰をループに変換
□投稿者/ R・田中一郎 (3回)-(2008/11/11(Tue) 11:03:33)
R・田中一郎 さんの Web サイト
> 再帰処理するミニマムコードを書く。
> スタックの深さと、ローカル変数を自分で管理すべく、スタックなコレクションなり、配列を操作する。
>
> って感じになるのかな?

説明が不足していました。
再帰を置き換えるのって、ちょっと面倒くさいから、最初にミニマムコード書いて置き換えてみて、要領をつかんでからの方がいいんじゃない?って意味です。
引用返信 編集キー/
■27662 / inTopicNo.7)  Re[3]: 再帰をループに変換
□投稿者/ Jitta (533回)-(2008/11/11(Tue) 21:35:25)
Jitta さんの Web サイト
No27539 (C学習中 さん) に返信
 C でクイックソートなら、qsort(3) を使う。いじょ

typedef struct {
    int* pArray;
    int count;
} SortIntStruct;
void SortInt(SortIntStruct* data);

int main(int argc, char** argv)
{
    int idx;
    int size, *org;
    SortIntStruct sortData;
    if (argc < 2) {
        return -1;
    }
    size = sizeof(int) * argc;
    org = (int*) malloc(size);
    memset(org, 0, size);
    for (idx = 1; idx < argc; ++idx) {
        org[idx - 1] = atoi(argv[idx]);
        printf("%d ", org[idx - 1]);
    }
    printf("\n");
    sortData.pArray = org;
    sortData.count = argc - 1;
    SortInt(&sortData);
    // TODO 表示
    free(org);
}

void SortInt(SortIntStruct* data)
{
    SortIntStruct *ptr = (SortIntStruct*) malloc(sizeof(SortIntStruct));
    ptr->pArray = &(data->pArray[0]);
    ptr->count = data->count;
    while (ptr != NULL) {
        // TODO ここで入れ替えとスタックへの積み込みをやる
        // ごめん、疲れた。。。
        free(ptr);
        ptr = Pop();
    }
}

// -----
static void* stack = NULL;
static int stackSize = 0;
void* Pop(void);
int Push(void* data);

int Push(void* data)
{
    int sz = stackSize + 1;
    void* ptr = NULL;
    if (stackSize == 0) {
        ptr = (void*) malloc(sizeof(void*) * stackSize);
    } else {
        ptr = (void*) realloc(stack, stackSize);
    }
    stack = ptr;
    memcpy(&stack[stackSize], data, sizeof(void*));
    stackSize = sz;
    return stackSize;
}

void* Pop(void)
{
    void* ptr = NULL;
    void* ret = NULL;
    if (stackSize == 0) { return NULL; }
    ret = stack[--stackSize];
    // 今までに確保した最大を別途保存しておけば、
    // 毎回サイズを縮小する必要はない。
    // ただし、いつかどこかで解放してやる必要がある。
    ptr = (void*) realloc(stack, stackSize);
    stack = ptr;
    return ret;
}

引用返信 編集キー/
■27668 / inTopicNo.8)  Re[4]: 再帰をループに変換
□投稿者/ C学習中 (5回)-(2008/11/11(Tue) 23:35:33)
2008/11/11(Tue) 23:39:19 編集(投稿者)

No27662 (Jitta さん) に返信

出水さん
R・田中一郎 さん
Jitta さん

回答有難うございます。

私は今 ポインタ、メモリ管理を学習中なので力不足で皆様の
回答のコメントはとてもできません。

推測で申し訳ないのですが、クイックソートは分割統治法なので
分割毎の結果を
出水さんはキューにつみ
R・田中一郎 さんはスタックにつみ
Jitta さんはメモリ管理でヒープに積むという
ことと思います。

そして再帰を避けてループにすると述べられていると思います。

(C#はキューとかスタックはあったと思いますがCにはなかったような?なんせC初心者ですから。)



Jitta さんのコードを参考にじっくりと学習させて頂きます。
丁度今学習中の部分がポインタとメモリ管理であり、よい教材になりそうです。
(学習と言っても本を読む独学です)

取り急ぎお礼の返事とさせて頂きます。



引用返信 編集キー/
■27669 / inTopicNo.9)  Re[4]: 再帰をループに変換
□投稿者/ あんどちん (23回)-(2008/11/12(Wed) 00:11:51)
No27662 (Jitta さん) に返信
> ■No27539 (C学習中 さん) に返信
>  C でクイックソートなら、qsort(3) を使う。いじょ
qsortって再帰しない保証ありましたっけ?
質問者の方は再帰をループにしたコードを知りたいと仰っておられるので気になりました。
与えられた物を使った方が良いと言われるのであればそれもわかりますが、アルゴリズムを勉強するのはそれとは別の話だと思います。

それと、
> static void* stack = NULL;
の宣言に対し
> memcpy(&stack[stackSize], data, sizeof(void*));
で第一引数に与えているアドレスは正しく取れないと思います。

引用返信 編集キー/
■27676 / inTopicNo.10)  Re[5]: 再帰をループに変換
□投稿者/ Jitta on the way (222回)-(2008/11/12(Wed) 07:54:40)
No27669 (あんどちん さん) に返信
> ■No27662 (Jitta さん) に返信
>>■No27539 (C学習中 さん) に返信
>> C でクイックソートなら、qsort(3) を使う。いじょ
> qsortって再帰しない保証ありましたっけ?
> 質問者の方は再帰をループにしたコードを知りたいと仰っておられるので気になりました。
> 与えられた物を使った方が良いと言われるのであればそれもわかりますが、アルゴリズムを勉強するのはそれとは別の話だと思います。

前の続きだとは思ったのですが、ハンドルには「学習中」と有りますが、質問文には書いてありません。また、何を勉強しているのか、主題がきになったので。
再帰をループで実現する考え方なのか。クイック ソートのアルゴリズムなのか。クイック ソートなら、分割の種の取り方が1つの肝です。そこを自力でゴソゴソやるより、用意されているものを利用するほうが良いでしょう。
また、関数名に Int を付けました。しかし、用意されたライブラリには付いていません。ソート関数を作ることが目的なら、自力で実装するより組み込み関数を使う方が良いと思いました。


>
> それと、
>>static void* stack = NULL;
> の宣言に対し
>> memcpy(&stack[stackSize], data, sizeof(void*));
> で第一引数に与えているアドレスは正しく取れないと思います。
>

あ……そうですね。え〜っと。あとは任せた>C学習中さん
引用返信 編集キー/
■27691 / inTopicNo.11)  Re[6]: 再帰をループに変換
□投稿者/ .SHO (18回)-(2008/11/12(Wed) 13:36:42)
アルゴリズムの勉強ということで、元の処理を極力破壊せずに
再帰をループにしました。
myutil.h はないので、SWAPはマクロにしました。
スタックサイズは、面倒なので100固定でオーバフローのチェックはしてません。

#include <stdio.h>
#define SWAP(a,b) {int w=a;a=b;b=w;}

static int data[] = { 3, 9, -8, 5, 9, 1, -2, -5, 13, 0 };

static int stackP, stack[100];

static void Push( int i, int j ) {
	stack[stackP++] = i;
	stack[stackP++] = j;
}

static int Pop( int *i, int *j ) {
	if ( stackP ) {
		*j = stack[--stackP];
		*i = stack[--stackP];
		return 1;
	}
	return 0;
}

static int Quicksort( int left, int right ) {
	int i, last;

	if ( left >= right )
		return -1;

	last = left;
	
	for ( i=left+1; i<=right; i++ ) {
		if ( data[left] >= data[i] ) {
			last++;
			SWAP( data[last], data[i] );
		}
	}
	SWAP( data[left], data[last] );
	return last;
}

int main() {
	int left, right, last, i, n = sizeof( data ) / sizeof( int );

	Push( 0, n - 1);
	while ( Pop( &left, &right ) ) {
		last = Quicksort( left, right );
		if ( last != -1 ) {
			Push( left    , last - 1 );
			Push( last + 1, right    );
		}
	}

	// 結果の確認
	for ( i=0; i<n; i++ )
		printf( "%d ", data[i] );
	printf( "\n" );

	return 0;
}

引用返信 編集キー/
■27694 / inTopicNo.12)  Re[7]: 再帰をループに変換
□投稿者/ .SHO (19回)-(2008/11/12(Wed) 14:08:51)
そういえば…

malloc、realloc、freeを繰り返すのが面倒だったので
簡易的にスタックサイズを100にしましたが、この処理の場合
計算量はO(log n)が最大なので、最初にmalloc一発でいけますね。
引用返信 編集キー/
■27706 / inTopicNo.13)  Re[5]: 再帰をループに変換
□投稿者/ R・田中一郎 (4回)-(2008/11/12(Wed) 18:06:36)
R・田中一郎 さんの Web サイト
> 推測で申し訳ないのですが、クイックソートは分割統治法なので
> 分割毎の結果を
> R・田中一郎 さんはスタックにつみ

そうです。

> (C#はキューとかスタックはあったと思いますがCにはなかったような?なんせC初心者ですから。)

用意されているのですが、要は、同じ処理を自分で書けよいのです。

キューは、FIFO(先入れ先出し) スタックは FILO(先入れ後だし) です。

再帰を置き換えるのがスタックである理由は、再帰が深くなっても、必ず最後に積んだものから復帰させていけば良いからです。

スタックのインデックスを 0〜9 に配置する(要するに 10 の深度まで想定する)場合、0 番目に格納されるのは、最上階にあるデータの情報で、下の階層に行く直前の内容を保持します。
下の階層から更に下の階層に潜る時は、1 番目に保持します。
こうすれば、逆に下から上がってきた場合は、インデックス値を -1 した要素の続きから、その階層の処理を再開すれば良いのです。
引用返信 編集キー/
■27718 / inTopicNo.14)  Re[8]: 再帰をループに変換
□投稿者/ 出水 (94回)-(2008/11/12(Wed) 20:45:33)
No27694 (.SHO さん) に返信
> そういえば…
>
> malloc、realloc、freeを繰り返すのが面倒だったので
> 簡易的にスタックサイズを100にしましたが、この処理の場合
> 計算量はO(log n)が最大なので、最初にmalloc一発でいけますね。

メジアンを必ず取れればそうなりますが、そうは問屋がおろさないのが難しいところです
引用返信 編集キー/
■27724 / inTopicNo.15)  Re[9]: 再帰をループに変換
□投稿者/ dogatana (4回)-(2008/11/12(Wed) 23:51:46)
No27718 (出水 さん) に返信
> ■No27694 (.SHO さん) に返信
>>そういえば…
>>
>>malloc、realloc、freeを繰り返すのが面倒だったので
>>簡易的にスタックサイズを100にしましたが、この処理の場合
>>計算量はO(log n)が最大なので、最初にmalloc一発でいけますね。
>
> メジアンを必ず取れればそうなりますが、そうは問屋がおろさないのが難しいところです

スタックの深さ≒再帰の深さでしょうか。
分割したとき要素が1の側はスタックにつまないという前提であれば、スタックは最大n個分で良いんですかね。

でも再帰の深さ≠計算量ですね?(確認)
ソートの場合、比較と交換のコストがかかりますが、計算量という場合は、特に区別しないのでしょうか?
(計算量という言葉はいつも感覚的に使用しているもので・・)

引用返信 編集キー/
■27727 / inTopicNo.16)  Re[10]: 再帰をループに変換
□投稿者/ れい (822回)-(2008/11/13(Thu) 00:29:05)
No27724 (dogatana さん) に返信
> >>計算量はO(log n)が最大なので、最初にmalloc一発でいけますね。
>>
>>メジアンを必ず取れればそうなりますが、そうは問屋がおろさないのが難しいところです

クイックソートの計算量はn^2が最大です。

> スタックの深さ≒再帰の深さでしょうか。

再帰をスタックで展開するのなら、
スタックの深さ=再帰の深さ
です。

> でも再帰の深さ≠計算量ですね?(確認)
> ソートの場合、比較と交換のコストがかかりますが、計算量という場合は、特に区別しないのでしょうか?
> (計算量という言葉はいつも感覚的に使用しているもので・・)

区別します。
ただ、一般にはそこまで言及しません。

"n"がコンテキストに応じて意味を変えるのと同様に、
計算量という単語もコンテキストに応じて使い分けられます。

掛け算を高速化したい、というコンテキストなら計算量は足し算や引き算の回数になります。
並び替えなら、要素一つの比較、もしくは要素の交換の回数が普通ですが、
要素をまとめて交換できるようなハードだとか、
比較にたくさんのコストがかかるようなデータを議論する場合は違う話になります。

引用返信 編集キー/
■27731 / inTopicNo.17)  Re[10]: 再帰をループに変換
□投稿者/ 出水 (95回)-(2008/11/13(Thu) 01:50:53)
> スタックの深さ≒再帰の深さでしょうか。
> 分割したとき要素が1の側はスタックにつまないという前提であれば、スタックは最大n個分で良いんですかね。
>
> でも再帰の深さ≠計算量ですね?(確認)
> ソートの場合、比較と交換のコストがかかりますが、計算量という場合は、特に区別しないのでしょうか?
> (計算量という言葉はいつも感覚的に使用しているもので・・)

計算量を間違った意味で使用しているのはわかってますが、
その辺は興味無いのでスルーしました

SHOさんが言いたいのは、スタックサイズは100でいいのかってところなんで
そこの部分は私も興味があるところです

で、結論から言うと、log2(n) でOKですね
メジアンを取れないと小さいサイズに収まらないってのは間違いでした
常に長い方をスタックに積めば一定の大きさ以上にはならないと思います
引用返信 編集キー/
■27736 / inTopicNo.18)  Re[11]: 再帰をループに変換
□投稿者/ なちゃ (203回)-(2008/11/13(Thu) 09:42:56)
あれ?nじゃなくて?
なんか勘違いしてますかね…

引用返信 編集キー/
■27738 / inTopicNo.19)  Re[12]: 再帰をループに変換
□投稿者/ επιστημη (1379回)-(2008/11/13(Thu) 10:05:09)
επιστημη さんの Web サイト
> あれ?nじゃなくて?
> なんか勘違いしてますかね…

ピボットを境に左右に振り分けたとき、最悪ケースではかたっぽの要素数が1となるわけで、
振り分けのたんびに 常に 1:のこりぜんぶ となるって悪夢が起こるなら振り分けをN回やらにゃ
なりません。
この振り分けのたんびにスタックを消費するのなら、スタックの"最悪消費量"はNに比例しますわね。

再帰 を スタック+loop に書き換えても本質的にはなんら変わらんのだから
書き換えたところで速くなるわけでもメモリをケチれるわけでもなく、
スタックの確保領域がヒープに置かれることでスタックオーバフローに対する
耐性が上がるってだけのことよねぇ?

引用返信 編集キー/
■27739 / inTopicNo.20)  Re[12]: 再帰をループに変換
 
□投稿者/ .SHO (21回)-(2008/11/13(Thu) 10:08:30)
れいさんが全て解決してくれてますが…^^;
(ありがとうございます)

>計算量はO(log n)が最大なので、最初にmalloc一発で…

というのは、Quicksort関数呼び出し1回を基本の計算量とした時に
再帰の深さ=スタックの深さ=単位計算量
という意味です。
引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -