前回までで、「旅立ちの支度」として一応C++とC#のコンソールプログラムとウィンドウプログラム(C#はWinFormsとWPF)で"Hello World!"を表示するだけのプロジェクトを作り、比較しながらVisual Studioがどのように動くのかの概観を見てまいりました。
今回は、旅へ出発するとすぐに経験する「戦い」の一つとして、
(1)前にやったこれ(【ソート】まずは比較コード(C++)の紹介)とこれ(【無駄話】"Comparison_Sort"をC#に移植-注)を使い、
注:尚、このプログラムには誤りがありました。C#のTimeSpanのMilliSecondsプロパティを使っていますが、これは通算の時間ではなく、MilliSecond単位の時間のみを返すようで、値が正しくなかったようです。MilliSecond→TotalMilliSecond修正したものを以下(↓)に再掲載します。
(2)Intel Core-i9の新PCで動くEmbarcadero C++ コンパイラー(32bit)とWindows付属の.NET Framework 4.8のC#コンパイラーを使って上記(1)のプログラムを走らせた結果を、
(3)AMD Ryze 7の旧PC上のVisual Studio 2026の最新バージョンのC++とC#(共に64bit)で走らせた結果と比較してみよう、
という子供じみた競争をやってみようと思います。
さて、どんな結果になるのやら?
【Comparison_Sort.cpp】
///////////////////////
// C++ Sort Comparison
///////////////////////
#include <stdio.h> //C言語の最小限のシステムヘッダー
#include <conio.h> //getch()を使う為
#include <iostream> //C++言語の入出力システムヘッダー
#include <chrono> //C++の時間計測用ライブラリー
//定数定義
#define MAX 10000 //取り敢えず10,000がお勧め
using namespace std;
////////////////////////
// 関数プロトタイプ宣言
////////////////////////
// Comparison
void comparison(int);
// Bubble Sort
void bubblesort(int*, int);
// Heap Sort
void heapsort(int*, int);
void exchange(int, int*, int);
// Quick Sort
void quicksort(int*, int*);
// Shell Sort
void shellsort(int*, int);
// Print result
void print_result(int);
// Comparison function
int compare(const void*, const void*);
////////////////////
// 共通テスト用配列
////////////////////
static int original[MAX];
static double results[4][MAX];
/////////////
// main関数
/////////////
int main(int argc, char** argv) {
chrono::system_clock::time_point begining = chrono::system_clock::now();
for(int i = 0; i < MAX; i++) {
comparison(i);
}
for(int i = 0; i < 4; i++) {
print_result(i);
}
chrono::system_clock::time_point ending = chrono::system_clock::now();
double t = static_cast<double>(chrono::duration_cast<chrono::seconds>(ending - begining).count());
printf("Total time consumed for the above processing is: %lf[s]\r\n", t);
cout << endl << "Push CR key ..." << endl;
getchar();
return 0;
}
///////////////
// Comparison
///////////////
void comparison(int n) {
//テスト用の配列の作成
srand((unsigned)time(NULL)); //現在時刻を元に種を生成
static int test_array[MAX]; //テスト用ローカル変数
for(int i = 0; i < MAX; i++) {
test_array[i] = //originalを保存し、テストはこれで行う
original[i] = rand(); //0から32,767の整数で初期化する
}
//時間計測の準備
chrono::system_clock::time_point start, end;
//ソート比較
start = chrono::system_clock::now();
bubblesort(test_array, MAX);
end = chrono::system_clock::now();
results[0][n] = static_cast<double>(chrono::duration_cast<chrono::microseconds>(end - start).count() / 1000.0);
for(int i = 0; i < MAX; i++)
test_array[i] = original[i];
start = chrono::system_clock::now();
heapsort(test_array, MAX);
end = chrono::system_clock::now();
results[1][n] = static_cast<double>(chrono::duration_cast<chrono::microseconds>(end - start).count() / 1000.0);
for(int i = 0; i < MAX; i++)
test_array[i] = original[i];
start = chrono::system_clock::now();
quicksort(test_array, &test_array[MAX - 1]);
end = chrono::system_clock::now();
results[2][n] = static_cast<double>(chrono::duration_cast<chrono::microseconds>(end - start).count() / 1000.0);
for(int i = 0; i < MAX; i++)
test_array[i] = original[i];
start = chrono::system_clock::now();
shellsort(test_array, MAX);
end = chrono::system_clock::now();
results[3][n] = static_cast<double>(chrono::duration_cast<chrono::microseconds>(end - start).count() / 1000.0);
}
////////////////
// Bubble Sort
////////////////
/*
n個の整数配列dataを総て順に比較、交換し、配列の最後から大きい順に並べる
通称「馬鹿ソート」
*/
void bubblesort(int* data, int n) {
int i, j, temp;
for(i = n - 1; i > 0; i--) { //配列の末尾から先頭へ
for(j = 0; j < i; j++) { //配列の先頭から末尾へ
if(data[j] > data[j + 1]) { //次の配列要素の方が小さければ交換する
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
} //その結果iに最大の要素が置かれる
}
}
//////////////
// Heap Sort
//////////////
/*
ヒープソートは次のようなヒープ(二分木)の「親-子」関係を用いる
Parent(0)
/\
ChildL(1) ChildR(2)
この二分木を用いて配列を表現すると以下のようになる(数字は添字)
0
/ \
1 2
/ \ / \
3 4 5 6
/ \
7 8 ...
*/
void exchange(int top, int* data, int n) {
int temp = data[top]; //開始時の二分木の親の値をtempに退避
//子のある二分木の左の子供を指定し、(交換があればやり直す為)末尾迄子孫を辿る
for(int i = top * 2 + 1; i < n; top = i, i = top * 2 + 1) {
if(i < n - 1 && data[i] < data[i + 1]) //右の子供が左の子供より大きければ
i++; //右の子供を指定する
if(temp >= data[i]) //開始時の親と大きい方の子供を比較
break; //親が大きければループを抜ける(topは変化しない)
data[top] = data[i]; //二分木の親に大きい子の値を代入する
} //大きい子(data[i])はループのi = topで
data[top] = temp; //data[top]となり、退避した親の値を代入
}
void heapsort(int* data, int n) {
int top, i;
for(top = n / 2 - 1; top >= 0; top--) //最初に「子がある最後の二分木」から
exchange(top, data, n); //「親が最大値を持つ」ように頂点まで継続する
for(i = n - 1; i > 0; i--) { //次に配列の最後と頂点(最大値)の値を交換し
int temp;
temp = data[i];
data[i] = data[0];
data[0] = temp;
exchange(0, data, i);
} //配列の最後を切り離し、その前の要素から頂点まで継続する
}
//////////////
// Quick Sort
//////////////
//検索範囲(startとend)で、基準値(key)を定め、それよりも多いものと少ないものを分割し、更にkeyの前後で同じ処理を再帰呼び出しする。
void quicksort(int* start, int* end) {
if(start < end) { //開始位置が終了位置以上で終了(最後の再帰呼び出し参照)
int *left, *right, key, *kp; //順に右進行位置、左進行位置、「分水嶺」値、その位置
//右端乃至左端に到達し、breakで抜けるまでループする
for(left = start, right = end, key = *right;;) {
while(key >= *left)
left++; //keyを超える値迄右に進む
if(left < right) //rightの位置迄は
*right = *left; //leftの値をrightの位置に書き込む
else { //leftがright位置を過ぎたなら
kp = right; //keyの位置をrightの位置としてループを抜ける
break;
}
while(key <= *right)
right--; //key未満の値迄右に進む
if(left < right) //leftの位置迄は
*left = *right; //rightの値をleftの位置に書き込む
else { //rightがleft位置を過ぎたなら
kp = left; //keyの位置をleftの位置としてループを抜ける
break;
}
}
*kp = key; //keyポインターの位置にkeyの値を代入する
quicksort(start, kp - 1); //開始位置からkeyポインター迄で再帰呼び出しする
quicksort(kp + 1, end); //keyポインターから終了位置迄で再帰呼び出しする
}
}
//////////////
// Shell Sort
//////////////
//シェルソートは配列要素を一定の距離を置いたグループに分けて比較、交換を行いソートする方法
void shellsort(int *v, int n) {
int gap, i, j, temp; //gapは比較、交換する要素間の距離
for(gap = n / 2; gap > 0; gap /= 2) { //初期的な距離は配列サイズの1/2とする
for(i = gap; i < n; i++) { //距離は徐々に狭まってゆく
for(j = i - gap; j >= 0 && v[j] > v[j + gap]; j -= gap) {
//v[j]とv[j + gap]を交換する
temp = v[j];
v[j] = v[j + gap];
v[j + gap] = temp;
}
}
}
}
/////////////////
// Print results
/////////////////
void print_result(int n) {
//double results[n][MAX]
char* title[4] = {">>> Bubble Sort <<<", ">>> Heap Sort <<<", ">>> Quick Sort <<<", ">>> Shell Sort <<<"};
double max = 0.0, min = (double)MAX, med = 0, ave = 0;
for(int i = 0; i < MAX; i++) {
if(results[n][i] > max) //n番目のソートの結果の最大値を求める
max = results[n][i];
if(results[n][i] < min) //n番目のソートの結果の最小値を求める
min = results[n][i];
ave += results[n][i]; //n番目のソートの結果を合計する
}
qsort(results[n], MAX, sizeof(double), compare);
med = results[n][MAX % 2 ? MAX / 2 : MAX / 2 - 1];
cout << title[n] << endl;
cout <<"最大値 :" << max << endl;
cout <<"最小値 :" << min << endl;
cout <<"平均値 :" << ave / MAX << endl;
cout <<"中央値 :" << med << endl;
cout <<"総所要時間:" << ave << endl << endl;
}
///////////////////////////////////////
//Comparison function
//戻り値 負-elem1がelem2より小さい
// 0 -elem1がelem2と等しい
// 正-elem1がelem2より大きい
///////////////////////////////////////
int compare(const void *elem1, const void *elem2) {
return (int)(*(double*)elem1 - *(double*)elem2);
}
【Comparison_Sort.cs】
///////////////////////
// C# Sort Comparison
///////////////////////
using System;
using System.Diagnostics; //Stopwatchを使う為
namespace Comparison_Sort
{
class comparison_sort
{
///////////
//定数定義
///////////
const int MAX = 10000; //取り敢えず10,000がお勧め
////////////////////
// 共通テスト用配列
////////////////////
int[] original = new int[MAX];
double[,] results = new double[4, MAX];
///////////////
//Mainメソッド
///////////////
public static void Main()
{
comparison_sort cs = new comparison_sort();
Stopwatch sw = new Stopwatch();
sw.Start();
for(int i = 0; i < MAX; i++) {
cs.comparison(i);
}
for(int i = 0; i < 4; i++) {
cs.print_result(i);
}
sw.Stop();
TimeSpan ts = sw.Elapsed; //時間ts.Hours, 分ts.Minutes, 秒ts.Seconds, ミリ秒ts.TotalMillisecondsはその位のみ(通算はTotalが必要)
string elapsed = String.Format("Total time(s) consumed for the above processing is: {0:0.000000}", ts.TotalSeconds);
Console.WriteLine(elapsed);
Console.WriteLine("Push CR key ..." + Environment.NewLine);
Console.Read(); //コンソールを閉じない為
}
///////////////
// Comparison
///////////////
void comparison(int n)
{
//乱数の初期化
Random rand = new Random((int) DateTime.Now.Ticks & 0x0000FFFF);
//テスト用の配列の作成
//解説:C#では引数に配列を渡す場合、参照渡しになる-https://water2litter.net/rye/post/c_array_ref/
int[] test_array = new int[MAX]; //テスト用ローカル変数
for(int i = 0; i < MAX; i++)
{
test_array[i] = //originalを保存し、テストはこれで行う
original[i] = rand.Next(); //0から32,767の整数で初期化する
}
//時間計測の準備
Stopwatch sw = new Stopwatch();
//ソート比較
sw.Start();
bubblesort(test_array);
sw.Stop();
TimeSpan ts = stw.Elapsed; //時間ts.Hours, 分ts.Minutes, 秒ts.Seconds, ミリ秒ts.TotalMillisecondsはその位のみ(通算はTotalが必要)
results[0, n] = (double)ts.TotalMilliseconds;
for (int i = 0; i < MAX; i++)
test_array[i] = original[i];
sw.Reset();
sw.Start();
heapsort(test_array);
sw.Stop();
TimeSpan ts = stw.Elapsed; //時間ts.Hours, 分ts.Minutes, 秒ts.Seconds, ミリ秒ts.TotalMillisecondsはその位のみ(通算はTotalが必要)
results[1, n] = (double)ts.TotalMilliseconds;
for (int i = 0; i < MAX; i++)
test_array[i] = original[i];
sw.Reset();
sw.Start();
quicksort(test_array, 0, MAX - 1); //配列、開始添字、終了添字
sw.Stop();
TimeSpan ts = stw.Elapsed; //時間ts.Hours, 分ts.Minutes, 秒ts.Seconds, ミリ秒ts.TotalMillisecondsはその位のみ(通算はTotalが必要)
results[2, n] = (double)ts.TotalMilliseconds;
for (int i = 0; i < MAX; i++)
test_array[i] = original[i];
sw.Reset();
sw.Start();
shellsort(test_array);
sw.Stop();
TimeSpan ts = stw.Elapsed; //時間ts.Hours, 分ts.Minutes, 秒ts.Seconds, ミリ秒ts.TotalMillisecondsはその位のみ(通算はTotalが必要)
results[3, n] = (double)ts.TotalMilliseconds;
}
////////////////
// Bubble Sort
////////////////
/*
n個の整数配列dataを総て順に比較、交換し、配列の最後から大きい順に並べる
通称「馬鹿ソート」
*/
void bubblesort(int[] data)
{
int i, j, temp, n = data.Length;
for(i = n - 1; i > 0; i--) { //配列の末尾から先頭へ
for(j = 0; j < i; j++) { //配列の先頭から末尾へ
if(data[j] > data[j + 1]) { //次の配列要素の方が小さければ交換する
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
} //その結果iに最大の要素が置かれる
}
}
//////////////
// Heap Sort
//////////////
/*
ヒープソートは次のようなヒープ(二分木)の「親-子」関係を用いる
Parent(0)
/\
ChildL(1) ChildR(2)
この二分木を用いて配列を表現すると以下のようになる(数字は添字)
0
/ \
1 2
/ \ / \
3 4 5 6
/ \
7 8 ...
*/
void exchange(int top, int[] data, int n)
{
int temp = data[top]; //開始時の二分木の親の値をtempに退避
//子のある二分木の左の子供を指定し、(交換があればやり直す為)末尾迄子孫を辿る
for(int i = top * 2 + 1; i < n; top = i, i = top * 2 + 1) {
if(i < n - 1 && data[i] < data[i + 1]) //右の子供が左の子供より大きければ
i++; //右の子供を指定する
if(temp >= data[i]) //開始時の親と大きい方の子供を比較
break; //親が大きければループを抜ける(topは変化しない)
data[top] = data[i]; //二分木の親に大きい子の値を代入する
} //大きい子(data[i])はループのi = topで
data[top] = temp; //data[top]となり、退避した親の値を代入
}
void heapsort(int[] data)
{
int top, i, n = data.Length;
for(top = n / 2 - 1; top >= 0; top--) //最初に「子がある最後の二分木」から
exchange(top, data, n); //「親が最大値を持つ」ように頂点まで継続する
for(i = n - 1; i > 0; i--) { //次に配列の最後と頂点(最大値)の値を交換し
int temp;
temp = data[i];
data[i] = data[0];
data[0] = temp;
exchange(0, data, i);
} //配列の最後を切り離し、その前の要素から頂点まで継続する
}
//////////////
// Quick Sort
//////////////
//検索範囲(startとend)で、基準値(key)を定め、それよりも多いものと少ないものを分割し、更にkeyの前後で同じ処理を再帰呼び出しする。
void quicksort(int[] data, int start, int end) //配列、開始添字、終了添字
{
if(start < end) //開始位置が終了位置以上で終了(最後の再帰呼び出し参照)
{
int left, right, key, kp; //順に右進行添字、左進行添字、「分水嶺」値、その添字
//右端乃至左端に到達し、breakで抜けるまでループする
for(left = start, right = end, key = data[right];;) {
while(key >= data[left])
{
left++; //keyを超える値迄右に進む
if(left == right) //配列添字が範囲を超えないよう追加
break;
}
if(left < right) //rightの位置迄は
data[right] = data[left]; //leftの値をrightの位置に書き込む
else //leftがright位置を過ぎたなら
{
kp = right; //keyの位置をrightの位置としてループを抜ける
break;
}
while(key <= data[right])
{
right--; //key未満の値迄右に進む
if(right == 0) //配列添字が範囲を超えないよう追加
break;
}
if(left < right) //leftの位置迄は
data[left] = data[right]; //rightの値をleftの位置に書き込む
else //rightがleft位置を過ぎたなら
{
kp = left; //keyの位置をleftの位置としてループを抜ける
break;
}
}
data[kp] = key; //keyポインターの位置にkeyの値を代入する
quicksort(data, start, kp - 1); //開始位置からkeyポインター迄で再帰呼び出しする
quicksort(data, kp + 1, end); //keyポインターから終了位置迄で再帰呼び出しする
}
}
//////////////
// Shell Sort
//////////////
//シェルソートは配列要素を一定の距離を置いたグループに分けて比較、交換を行いソートする方法
void shellsort(int[] data)
{
int gap, i, j, temp, n = data.Length; //gapは比較、交換する要素間の距離
for(gap = n / 2; gap > 0; gap /= 2) { //初期的な距離は配列サイズの1/2とする
for(i = gap; i < n; i++) { //距離は徐々に狭まってゆく
for(j = i - gap; j >= 0 && data[j] > data[j + gap]; j -= gap) {
//v[j]とv[j + gap]を交換する
temp = data[j];
data[j] = data[j + gap];
data[j + gap] = temp;
}
}
}
}
/////////////////
// Print results
/////////////////
void print_result(int n)
{
double[] array = new double[MAX]; //n行の配列のコピー用
string[] title = new string[4] {">>> Bubble Sort <<<", ">>> Heap Sort <<<", ">>> Quick Sort <<<", ">>> Shell Sort <<<"};
double max = 0.0, min = (double)MAX, med = 0, ave = 0;
for(int i = 0; i < MAX; i++)
{
if(results[n, i] > max) //n番目のソートの結果の最大値を求める
max = results[n, i];
if(results[n, i] < min) //n番目のソートの結果の最小値を求める
min = results[n, i];
ave += results[n, i]; //n番目のソートの結果を合計する
array[i] = results[n, i]; //n番目の配列をコピーする
}
Array.Sort(array); //コピー配列をソートする
med = array[(MAX % 2 != 0) ? MAX / 2 : MAX / 2 - 1];
Console.WriteLine(title[n]);
Console.WriteLine("最大値 :{0}", max);
Console.WriteLine("最小値 :{0}", min);
Console.WriteLine("平均値 :{0}", ave / MAX);
Console.WriteLine("中央値 :{0}", med);
Console.WriteLine("総所要時間:{0}", ave);
Console.WriteLine();
}
}
}