[アルゴリズム] 選択ソートの実装(C言語編)

C言語って高速に動作するアプリケーションが作れるけどコンパイル言語って面倒くさいな〜って思っていましたが、
実はそれは、「ポインタ」を勉強することを避けてきた事も原因でした。
今回、関数を実践で使うことによって、ポインタについて非常によく理解することができました。
そして、C言語の色々なルールや法則をここにきてようやく理解できたので、この企画やっていて本当に良かったと思える瞬間でしたね。
という訳で、選択ソートのC言語編ですが、構築時間は30分程度でしたが、少し調査に時間をかけた内容でした。
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#include <stdio.h> void selectSort(int *nums,int numsCount); void arrayView(int *nums,int numsCount); int main(void){ int numbers[] = {10,2,12,7,16,8,13}; int numbersCount = (sizeof numbers / sizeof numbers[0]); selectSort(numbers,numbersCount); arrayView(numbers,numbersCount); return 0; } void selectSort(int *nums,int numsCount){ // 変数の定義 int min,num ,i,j; // 数値配列を最初から順番に評価していく for (i=0; i<numsCount; i++) { // 最小値の格納変数(最初は評価対象数値を入れておく) min = nums[i]; // 入れ替え対象番号用変数 num = i; //評価番以降の一番低い数値を取得 for(j=num+1; j<numsCount; j++){ if(min > nums[j]){ num = j; min = nums[j]; } } // 評価番号と入れ替え番号が同じ場合は入れ替えなしとして処理無し if(num == i){continue;} // 評価番号と入れ替え番号の値を入れ替える nums[num] = nums[i]; nums[i] = min; } } void arrayView(int *nums,int numsCount){ int i; for (i=0; i<numsCount; i++) { printf("%d\n",nums[i]); } } |
結果
1 2 3 4 5 6 7 8 9 |
$ gcc -o selectSort selectSort.c $ ./selectSort 2 7 8 10 12 13 16 |
無事にソートされてて、一件落着です。
解説
前回のバブルソートでは、main関数だけで処理しましたが、今回は全ての言語で関数を基準に汎用性構築を行うという趣旨なので、C言語でも行なってみましたが、今まで知らなかった事がいくつかありました。
・関数の返り値に変数を使ってはいけない。
・ポインタで変数を受け渡すと、宣言の仕方によっては配列のサイズが変わってしまう
C言語に長けている人にとっては当たり前のことかもしれませんが、今どきのインタプリタ系言語では、このC言語のルールがイマイチ理解しがたい状態なのです。
C言語自体が、宣言という面倒くさい手続きがあるため、言語自体が面倒くさい言語という認識になってしまっているんでしょうね。
関数の返り値
今回は「selectSort」という基本コアエンジン部分の関数と、「arrayView」という表示を行う2つの関数を構築しましたが、
オブジェクト思考で考えると、selectSortに配列を渡して、ソート処理をしたデータをreturnして、受け取ったmain関数でそのままarraySortに受け流すという事が一般的なフローと考えられますが、ここでは、乱数配列部分をポインタとして扱うことで、returnを有しないvoid関数にして、直接元の変数を上書きしているという流れです。
便利とは言えないが、メモリの無駄使いをしないというポインタ、今まで本当に意味が分からなかったんですが、こういう事だったんですね。
関数に渡した配列のサイズ
今回宣言時に「int numbers[] = **」として任意サイズで受け取る仕様にしているんですが、numbers[10]のようにサイズ指定してあれば、さほど気にしなくても良いんですが、こういう処理って実際に個数が可変する汎用性って必須じゃないですか?
という事でこの仕様で関数に渡すと、main関数では、28バイトのメモリサイズに対して、関数に渡したあとでは、8バイトという結果になってしまいました。
部分的なソースコードですが、以下のような詳細です。
1 2 3 4 5 |
int numbers[] = {10,2,12,7,16,8,13}; int a = sizeof numbers; printf("numbers:%d\n",a); //結果 -> numbers:28 |
1 2 3 4 5 6 |
void arrayView(int *nums){ int a = sizeof nums; printf("nums:%d\n",a); } //結果 -> nums:8 |
どうやらこれは、任意個数での宣言と、受け渡した配列の型が違っていることが原因なようです。
下記に書かれていました。勉強になりました。
文字配列を関数に受け渡すとsizeof() 関数の値が変わる。
ポインタとして、同じ配列を渡していると思ってたのにそもそもの器が違う状態ってどういう事なのか???
numbers[]と*numbersの違いと言われるとわかった気になりますが、やはりわからないですね。
こういう時どうすればいいかというと、「そのうち分かるでしょう!!」とその場をスルーします。
ただし、こういう状況がアリ得るということを忘れてはいけませんね。
関連リンク
選択ソート
解説
Javascript
PHP
Python
Shell
Awk
C言語