• 締切済み

qsortと動的確保の2次元配列

C言語で以下のようなソートのあるプログラムを作ろうとしているのですが、良い方法が思いつきません。。。。 どなたか,知恵を貸していただけないでしょうか? ・複数人の身長と体重がcsvファイルに2列に入っている。 人 身長 体重 1 158.9 50.5 2 161.2 72.3 3 150.4 42.8 4 170.7 80.4 5 165.0 59.9 ・ ・ ・ ・ ・ ・ ・↑このように身長も体重もランダムに並んでいる状態 ・身長・体重をプログラムで読み込んだら 身長の低い順にソートする。この時体重も身長に対応して並び換わってほしい。 (わかりやすいかと思い人の番号列を設けましたが、人の番号は考えなくて良いです) この問題に対して,データ数が不特定かつ多いため 動的確保の2次元配列を使ったクイックソートで対応を考えます。 qsortについてあれこれ調べていたのですが,動的確保でのqsort例が無く困っています。。。 どなたかちょっとアドバイスをいただけないでしょうか? よろしくお願いします。 #include <stdio.h> #include <stdlib.h> enum {HIGHT, WEIGHT, COLUMN}; int comp (const void *a, const void *b) //比較関数 { return (int) (((double *)a)[HIGHT] - ((double *)b)[HIGHT]) ; } int main(void) { ////////////////////////////////////////////////////////// 私情により、ソート以前の処理で使用するため あらかじめ .csv ファイルから fget で値を読み込んで コンマで分割しx[]y[]に格納してある。 &データ数をカウントしている 今は省略  仮定として以下のように5つのデータ数を読み込んでいたとする double x[5] = {158.9,161.2,150.4,170.7,165.0}; double y[5] = {50.5,72.30,42.8,80.4,59.9}; int n=5; //データカウント数 /////////////////////////////////////////////////////////// int i; double **list; list = (double**)malloc(sizeof(double)*5); for (i=0 ; i< 5 ; i++) { list[i] = (double*)malloc(sizeof(double)*COLUMN); if (list[i]==NULL) return 1;/* 領域確保に失敗したか */ } for(i=0;i<n;i++) { list[i][HIGHT]=x[i]; list[i][WEIGHT]=y[i]; } for(i = 0; i < n; i ++) printf("%lf %lf\n", list[i][HIGHT], list[i][WEIGHT]); puts("Sort"); qsort(list,n, sizeof(double [COLUMN]), comp); for(i = 0; i <n; i ++) printf("%lf %lf\n", list[i][HIGHT], list[i][WEIGHT]); scanf("%d",i); return 0; }

  • gbcm
  • お礼率33% (2/6)

みんなの回答

  • chie65535
  • ベストアンサー率43% (8523/19372)
回答No.6

追記の追記の追記。 「単純な構造体にして、構造体の配列を、qsortにソートさせる」と言う方が、ずっと簡単で、メモリの確保や解放も1回で済みます。 しかし「データ件数が膨大になる」と「qsortが、構造体要素を入れ替えする時間」が無視出来なくなります。 doubleの要素が2つある構造体配列だと、最低でも1要素は16バイトです。 qsortは、入れ替えが必要になるたびに「16バイトを入れ替え」します。 なので(そう書くつもりじゃなかったとしても、偶然の結果)質問者さんが書いたような「ポインタが並んでるリスト構造」にして「qsortが入れ替えるのは、リストの中のポインタだけ」にすれば、入れ替えは「ポインタのサイズ(つまり、4バイト)」だけで済みます。 例えば、データ項目が「double 2つ」ではなく「double 2つと、datetime型の日付と、長さ256バイトの氏名」だったとします。 qsortに「構造体をそのまま入れ替えさせる」と「1件並び替え」するごとに、なんと、300バイト近くのデータを入れ替えます。 もし、件数が1000件あったら…。 リストの中のポインタだけqsortに並び替えさせれば、構造体の大きさが300バイトだろうが、3000バイトだろうが、入れ替えるのは4バイトです。 ただ、リスト構造にすると「メモリの確保と解放の処理が面倒」になりますので、それが欠点です。

gbcm
質問者

お礼

丁寧に教えてくださって本当にありがとうございます! 無事に動くようになりました。 初心者なりにいろいろ調べてみたものの,いろいろ途中と半端な状態でお願いてしまってお恥ずかしいです; 非常に勉強になりました。ありがとうございました!! (> <

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

どうでもいいところですが, list = (double**)malloc(sizeof(double)*5); って意味的には間違ってますよね. 個人的には list = (double**)malloc(sizeof(list[0])*5); のような形の方が好み. あと, 将来のことを考えるとデータの持ち方を考えた方がいいかもしれない. 「線形リストでもってマージソート」というのも選択肢に挙げておいたほうが良いと思います.

gbcm
質問者

お礼

ご指摘ありがとうございます! ”線形リストでもってマージソート” ですか マージソートはなんとなくわかりますが 線形リストはノードを自由に増やせるとかって事くらいしかわからないのが現状です;; これをきに勉強してみたいと思います♪

  • chie65535
  • ベストアンサー率43% (8523/19372)
回答No.4

追記の追記。 動的に確保したメモリは、使い終わったら解放しましょう。 return 0; ↓ for (i=0 ; i< 5 ; i++) { if (list[i]!=NULL) free(list[i]); } free(list); return 0; また、listの確保に失敗した時や、list[i]を途中まで確保して失敗した時は、解放してからreturnしましょう。 int i; ↓ int i,j; list = (double**)malloc(sizeof(double)*5); ↓ list = (double**)malloc(sizeof(double *)*5); /*さっき、ここ、見落とし*/ if (list==NULL) return 1;/* 領域確保に失敗した */ if (list[i]==NULL) return 1;/* 領域確保に失敗した */ ↓ if (list[i]==NULL) { for(j=i-1;j >= 0;j--) if (list[j]!=NULL) free(list[j]); /* 途中まで確保したのを解放 */ free(list); return 1;/* 領域確保に失敗した */ } 何度も何度も繰り返し実行していると、メモリリークが発生するので「mallocしたら必ずfree」を徹底しましょう。

  • chie65535
  • ベストアンサー率43% (8523/19372)
回答No.3

すいません、嘘書きました。 >そこさえ直せば「取りあえず、現状のままでも動く」と思います。 動きません。 list配列の構造と、qsortに渡すパラメータが一致してません。 このプログラムはメモリを破壊し、最悪、例外を出力して落ちます。 listの示すポインタのメモリには「doubleへのポインタが5つ並んでるだけ」です。 普通に list[i][HIGHT]=x[i]; list[i][WEIGHT]=y[i]; として、まるで2次元配列のようにアクセス出来てしまうので、2次元配列と勘違いしてしまいがちですが「実体は全然違う」のです。 そして、comp関数には、&list[0]や&list[1]が、つまり「double **」が渡されてきます。 comp関数の中で return (int) (((double *)a)[HIGHT] - ((double *)b)[HIGHT]) ; と評価すると、double **の場所を、double *[HIGHT]としてアクセスするので、訳の判らない評価をします。 しかも、qsortは、要素の大きさが「sizeof(double [COLUMN])」だと思って、listの要素を入れ替えしようとします。 しかし、listには「doubleへのポインタが5つ並んでるだけ」なので、訳の判らないメモリを訳も判らずに並び替えします。 そして、不正なメモリにアクセスし、例外を発生して落ちます。 以下の3点を変更すれば、こんどこそ、正常に動きます。 return (int) (((double *)a)[HIGHT] - ((double *)b)[HIGHT]) ; ↓ return (int) (((*(double **)a)[HIGHT] - (*(double **)b)[HIGHT])*10) ; qsort(list,n, sizeof(double [COLUMN]), comp); ↓ qsort(list,n, sizeof(double *), comp); scanf("%d",i); ↓ scanf("%d",&i);

  • chie65535
  • ベストアンサー率43% (8523/19372)
回答No.2

comp関数の >return (int) (((double *)a)[HIGHT] - ((double *)b)[HIGHT]) ; はバグってる。 「身長差が0.2の時、intにキャストしたらどうなるか?」を考えてみよう。 「両方を10倍してから引き算してintにする」とか「引き算した結果を10倍してからintにする」とか「if文を使う」とか「3項演算子を使う」とかしないと、ちゃんとソートされません。 そこさえ直せば「取りあえず、現状のままでも動く」と思います。

  • Yanch
  • ベストアンサー率50% (114/225)
回答No.1

1行分のレコードを一件のデータとして扱えるように、 構造体を用意すると良いと思います。

関連するQ&A

  • 2次元配列の動的確保について

    共分散行列を2次元配列に格納しようとしているのですが、 その要素は、左下半分と右上半分が同じになるため 対角要素と、どちらか半分だけを格納してメモリを節約したいと考えています。 以下のように動的確保することでメモリは節約できているでしょうか? if( (a = (double **)malloc(sizeof(double *) * (N) ))==NULL ){ fprintf(stderr, "error malloc for a\n"); exit(0); } for( i=0; i<NN; i++ ){ if( ( a[i] = (double *)malloc(sizeof(double) * (i+1) ))==NULL ){ fprintf(stderr, "error malloc for a\n"); exit(0); } } *節約しない場合は、i+1 が N になります。 確保できているのなら、どのように参照すればいいのでしょうか?データの並び(?)は、a[0][0],a[1][0],a[1][1],a[2][0],a[2][1],a[2][2],,,というように並んでいるでしょうか? 例えば、a[0][1]を参照しようとすると、シグメンテーションフォルトなど起こりうるでしょうか。必要であれば、上プログラム内Nは、3000程度と考えてください。 そして、もし他にメモリを節約する上で良い方法があれば、ご教授していただけたらと思います。 よろしくお願い致します。

  • qsortについて

    qsort()で構造体をソートする時に、qsortの第3,4引数時の、「size t_t size, int (*cmp)(const void *a, const void *b)」の使い方が分かりません。 何故これを使ってソートが出来るのでしょうか?そもそも、構造体を送った時に、自作関数内での書き方にも疑問があります。 int main(void){ MAX dt[5]={・・・・}; //構造体変数宣言 ・・・・・ qsort(dt,5,sizeof(MAX),sum); //(*1) .... return 0; } int sum(const void *a, const void *b){ MAX *p1 = (MAX *)a; //(*2) MAX *p2 = (MAX *)b; //(*2) ・・・・ return ~; } ---------------------------------------------------- *1: 第3引数のsizeof(MAX)って何ですか? *2: 例えばint型が渡されてreturnする場合は、「return *(int *)a - *(int *)b 」でいいのに、   何故構造体の場合はこう書かないといけないのですか?

  • 2次元配列を動的に確保する方法

    /*ネットの*/ w = (double **)malloc(sizeof(double*) * h); w[0] = (double *)malloc(sizeof(double) * v * h); for(i=0; i<h; i++) w[i] = w[0] + i *v; /*自分の*/ w = (double **)calloc(h, sizeof(double *)); for(i = 0; i < h; i++){ w[i] = (double *)calloc(v, sizeof(double)); 自分は初心者です。自分は下のようなコードで書いていたのですが ネットで見つけたコードで上のようにデータの領域を全部確保してから 2番目以降のポインタを割り当てるようなやり方をしていました。 そのやり方は初めて見たのですが、こっちのほうが一般的なやり方なんでしょうか? それと何かメリットはあるんでしょうか? 一度に確保した方が確実に連続領域に割り当てられるとかかなぁとか予想してるんですが・・・

  • 二次配列のqsort

    二次配列のqsortについて分かる方に教えて頂きたいのですが 一段落のプログラムを載らせていただきました.count3[j][i]をバブルソートで降順でやってみましたが高速が要求されるため,qsortを使ってやり直したいのですが (ちなみにcount1[j][i],count2[j][i]は前で定義してあります.count4[j][i]にはiの順番を記憶するための二次配列です)  ぜひともよろしくおねがいします. int ind_near_search(int j,int t) { int i,var_num,count3[IND][VAR],count4[IND][VAR],temp1,temp2,num=0,m=0; for(i=0;i<VAR;i++){ if(individual[j].x[i]==1){ //変数が1と0の場合分け count2[j][i]=t-count[j][i]; }else{ count2[j][i]=count[j][i]; } if(individual[j].x[i]==1){ //全てcount3に値を入れる count3[j][i]=count2[j][i]; }else{ count3[j][i]=count[j][i]; } } for(i=0;i<VAR;i++){ count4[j][i]=num++; } for(m=0;m<VAR-1;m++){ for(i=0;i<VAR;i++){ //バブルソートにより降順に並べ換え if(count3[j][i]<count3[j][i+1]){ temp1=count3[j][i]; count3[j][i]=count3[j][i+1]; count3[j][i+1]=temp1; temp2=count4[j][i]; //count4にはcount3の並べ替え後の対応する番号を入れる count4[j][i]=count4[j][i+1]; count4[j][i+1]=temp2; } } } for(i=0;i<VAR;i++){ var_num=count4[j][i]; //count4の大きい順番からその番号をvar_numに渡す if(individual[j].x[var_num]==0){//0と1の場合分け individual[j].x[var_num]=1; }else{ individual[j].x[var_num]=0; }

  • C言語の構造体についてです。

    構造体employee 型の変数を配列で5つ宣言し、BMI 値、肥満率、理想体重を求めた後、BMI 値の昇順でソートをかけ、表示をするというプログラムを作成したいのですが、うまくいきません。 ・引数は、配列(構造体employee []型 または、構造体employee *型) ・戻り値は、なし。 ・関数名は、sort ・番号はint型 ・名前はchar型 ・身長・体重・BMI値・肥満率・理想体重はdouble型 という指定があります。 #include <stdio.h> #define N 5 struct employee{ int number; char name[20]; double height; double weight; double bmi; double fat; double ideal; }; void bmi_fat(struct employee *); void sort(struct employee []); int main(void) { int i; struct employee std[N]={ {1, "oohata", 173.0, 60.0, 0.0, 0.0, 0.0},{2, "suzuki", 168.8, 60.2, 0.0, 0.0, 0.0}, {3, "satou", 168.2, 80.5, 0.0, 0.0, 0.0},{4, "tanaka", 162.5, 45.2, 0.0, 0.0, 0.0}, {5, "yamada", 155.3, 55.7, 0.0, 0.0, 0.0} }; for(i=0; i<N; i++){ sort(std); bmi_fat(&std [i]); } for(i=0; i<N; i++){ sort(std); printf("社員番号:%d番\n", std[i].number); printf("名前:%s\n", std[i].name); printf("身長:%2.1lfcm\n", std[i].height); printf("体重:%2.1lfkg\n", std[i].weight); printf("BMI:%2.1lf\n", std[i].bmi); printf("肥満率:%2.1lf%\n", std[i].fat); printf("理想体重:%2.1lfkg\n", std[i].ideal); } return 0; } void sort(struct employee a[]) { int i, j; struct employee x; a->bmi=a->weight/((a->height/100.0)*(a->height/100.0)); a->ideal=(a->height-100)*0.9; a->fat=a->weight/a->ideal*100.0; for(i=0; i<N-1; i++){ for(j=0; j<N-i-1; j++){ if(a[j].bmi>a[j+1].bmi){ x=a[j]; a[j]=a[j+1]; a[j+1]=x; } } } } 以上が作成済みのプログラムなのですが、「外部シンボル'_bmi_fat'が未解決」と出てしまいます。 どこを直してよいのかが全く分かりません。 ご教授お願い致します。

  • 二次元配列とポインタについてよくわかりません2

    先ほどもしつもんして自分なりに改良をくわえたのですが 上手くいきません。 int main( void ) {  int i, j;  /*4 x 4 行列 A*/  double a[4][4];  double *pa;  /*4 x 4 行列 B*/  double b[4];  double (*pb)[4];  /*4 x 4 行列 C*/  double c[4];  double (*pc)[4];  pa = a;  pb = b;  pc = c;  printf("4 x 4 行列 Aの要素を入力してください>\n");  for(i = 0; i < 4; i++){   for(j = 0; j < 4; j++){    printf("A[%d,%d]=", i + 1, j + 1 );    scanf("%lf", (*(pa + i) + j)); }} printf("4 x 4 行列 Bの要素を入力してください>\n"); for(i = 0; i < 4; i++){  for(j = 0; j < 4; j++){   printf("B[%d,%d]=", i + 1, j + 1 );   scanf("%lf", &pb[i][j]); }} としたところ(途中までを表記してます)  pa = a;  pb = b;  pc = c; のところでコンパイル時に 警告で問題のあるポインタの変換とでます なぜおかしいのかまったくわからないので よければ教えてください

  • 2次元配列の動的確保

    ある画像を読み込むため、その画像を格納できる幅、高さを持った配列を動的に確保しようと考えています。 幅をxsize、高さをysizeで次のように記述しました。 unsigned char **src; int i; src = (unsigned char**)malloc(sizeof(unsigned char*) * ysize); src[0] = (unsigned char*)malloc(sizeof(unsigned char) * xsize * ysize); for(i=1; i<ysize; i++) src[i] = (src[0] + i * xsize); わざわざポインタのポインタを使用したのは、動的に確保した配列を2次元的なアクセスをしたかったためです。 画像の読み込み時は fread(src[0], sizeof(unsigned char), xsize * ysize, fp); としています。 上に記述したソースは問題なく動作しました。 しかし、上の場合だと全ての配列を連続して確保することができません。つまりsrcでmallocを一回、src[0]でmallocを一回使っているため、ポインタの配列の直後に配列を確保する保障がありません。そこでいっぺんに確保することを考えました。 unsigned char **src; int i; src = (unsigned char **)malloc(sizeof(unsigned char *) * ysize + sizeof(unsigned char) * xsize * ysize); for(i=0; i<ysize; i++) src[i] = (unsigned char *)(src + sizeof(unsigned char *) * ysize + i * xsize); このように組み上げ、読み込み時は上のfreadと同様に記述したところエラーが出てしまいました。 やはり一行目のmallocで無理やりsizeof(unsigned char *) * ysize + sizeof(unsigned char) * xsize * ysize分確保するのは失敗だったのでしょうか?

  • mallocで動的確保後、値が変わる

    最近よく出くわすバグなのですが、mallocでメモリを動的確保して、いくつかの処理をした後に値が変わっていたり、読めなくてセグメンテーションエラーになることがあります。 根本的に何か間違っているのかもしれませんが、なかなかわかりません。 例えばこんな感じです ... int main(int argv,char *argc[]) { double *arrXn; double *arrFn; double *arrX; double *arrY; double *arrV; double *arrD; double **arrCn; ... //N(0~9)を受け取る arrXn=(double *)malloc(sizeof(double)*(N+1)); arrFn=(double *)malloc(sizeof(double)*(N+1)); arrX=(double *)malloc(sizeof(double)*(N+1)); arrY=(double *)malloc(sizeof(double)*(N+1)); arrV=(double *)malloc(sizeof(double)*(N+1)); arrD=(double *)malloc(sizeof(double)*(N+1)); arrCn=(double **)malloc(sizeof(double *)*(N+2)); for(incA=0;incA<=N;incA++){ arrCn[incA]=(double *)malloc(sizeof(double)*4); } for(incA=1;incA<N;incA++){ arrCn[incA][0]=arrFn[incA-1]; arrCn[incA][1]=arrV[incA-1]; arrCn[incA][2]=-3*arrFn[incA-1]+3*arrFn[incA]-2*arrV[incA-1]-arrV[incA]; arrCn[incA][3]=2+arrFn[incA-1]-2*arrFn[incA]+arrV[incA-1]+arrV[incA]; } printf("%lf",arrCn[1][1]);getchar(); //取れる for(incA=0;incA<=numHokan;incA++){ arrX[incA]=Setx(incA); //自作関数 } printf("%lf",arrCn[1][1]);getchar(); //セグメンテーションエラー 特に多次元配列を作成したり、動的確保を大目にすると起こりやすいです。 もし何かお気付きのことがありましたらよろしくお願いします。

  • 配列の動的確保を関数化

    main()内でいくつかの配列を動的確保するとごちゃごちゃするので、 関数化してみました。しかし、下のプログラムではうまい事いきませ んでした。 #include<stdio.h> #include<stdlib.h> void array1d(int *box,int n) { int i; box=(int *)malloc(n * sizeof(int)); for(i=0; i<n; i++) { box[i]=0; } } main() { int *pol,i,n; printf("N pol\nN = "); scanf("%d",&n); array1d(pol,n); for(i=0; i<n; i++) { printf("[%d]=%d\n",i,pol[i]); } } 実行結果は [0]=-14646387 [1]=-1819410433 [2]=-224 array1d内では0を格納していますが、このようになりました。 どのようにすれば 0になるでしょうか? 回答よろしくお願いします。

  • 多次元配列のソートがうまくいかない

    多次元配列のソートがうまくいかない 質問失礼します. 以下のような,String型,int型,double型の混在した多次元配列([3][3]の配列)をソートするプログラムを作成しました. このプログラムでは3番目の項目でソートを行っています. 問題点なのですが, 3番目の項目がdouble型の一桁(例えばarray[1][2]が2.0)ならばうまくソートできるのですが, 一つを2桁(例えばarray[1][2]を10.0)にすると何故か先頭の数(10.0の場合1)を基準にソートされてしまっているようです・・・ 配列へのデータの入れ方が間違っているのでしょうか? 原因がはっきりわからず困っているのですが, わかる方いましたらよろしくお願いします. public class Sort_test { /** * @param args */ public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ String[][] array = new String[3][3]; array[ 0 ][ 0 ] = "A"; array[ 0 ][ 1 ] = 2001+""; array[ 0 ][ 2 ] = 9.0+""; array[ 1 ][ 0 ] = "B"; array[ 1 ][ 1 ] = 1001+""; array[ 1 ][ 2 ] = 2.0+""; array[ 2 ][ 0 ] = "C"; array[ 2 ][ 1 ] = 3001+""; array[ 2 ][ 2 ] = 6.0+""; TheComparator comparator = new TheComparator(); // 3番目の項目でソートするように設定 comparator.setIndex( 2 ); // ソート実施 Arrays.sort( array, comparator ); dump(array); } public static void dump( String[][] array ) { for ( int i = 0;i < array.length;i++ ) { for ( int j = 0; j < array[ i ].length;j++ ) { System.out.print( "\t" + array[ i ][ j ] ); } System.out.println(); } } } //多次元配列ソート用クラス class TheComparator implements Comparator { /** ソート対象のカラムの位置 */ private int index = 0; /** ソートするためのカラム位置をセット */ public void setIndex( int index ) { this.index = index; } public int compare( Object a, Object b ) { String[] strA = ( String[] ) a; String[] strB = ( String[] ) b; return ( strA[ index ].compareTo( strB[ index ] ) ); } }

    • ベストアンサー
    • Java

専門家に質問してみよう