C++でマルチスレッドを使ってマージソートを実装する方法
本記事では、ソートされていない整数型配列が与えられたとき、マルチスレッドを活用したマージソート(Merge Sort)で配列を並べ替える方法を解説します。
マージソートとは
マージソートは「分割統治法(Divide and Conquer)」に基づくソートアルゴリズムです。配列を半分ずつに分割していき、最後にそれらを整列させながら統合(マージ)することで、全体を昇順に並べ替えます。
マージソートのアルゴリズム
- リスト内の要素が1つだけなら、その要素をそのまま返します。
- そうでなければ、データを再帰的に2つに分割し、それ以上分割できなくなるまで繰り返します。
- 最後に、小さなリスト同士を整列順に保ちながら新しいリストへマージしていきます。
マルチスレッドとは
オペレーティングシステムにおけるスレッド(Thread)は、タスクの一部を実行する軽量プロセスです。複数のスレッドは共通のリソースを共有しながら、処理を並行して実行できます。
マルチスレッドはマルチタスクの一種の実装方式で、1つのプロセッサ上で複数のスレッドを動かし、タスクを同時に実行することを可能にします。1つのアプリケーション内部の特定の処理を個別のスレッドに細分化し、それぞれのスレッドを並列に走らせることで、処理速度の向上が期待できます。
実行例
入力:int arr[] = {3, 2, 1, 10, 8, 5, 7, 9, 4}
出力:ソート済み配列: 1, 2, 3, 4, 5, 7, 8, 9, 10
説明:整数値を持つ未ソート配列が与えられます。これをマルチスレッドによるマージソートで並べ替えます。
入力:int arr[] = {5, 3, 1, 45, 32, 21, 50}
出力:ソート済み配列: 1, 3, 5, 21, 32, 45, 50
説明:こちらも同様に、未ソートの整数配列をマルチスレッド版マージソートで整列させます。
プログラムのアプローチ
- C++ STL の rand() メソッドを使って乱数を生成し、配列の初期値とします。
- pthread_t 型の配列 P_TH[thread_size] を作成します。
- i = 0 から i がスレッド数未満になるまでループを回し、pthread_create(&P_TH[i], NULL, Sorting_Threading, (void*)NULL) を呼び出してスレッドを生成します。
- pthread_join() ですべてのスレッドの完了を待ち合わせた後、combine_array(0, (size / 2 - 1) / 2, size / 2 - 1)、combine_array(size / 2, size/2 + (size-1-size/2)/2, size - 1)、combine_array(0, (size - 1)/2, size - 1) の順に呼び出して、各部分配列を統合します。
- 整数型配列 arr[] に格納されたソート結果を出力します。
関数 void* Sorting_Threading(void* arg) の内部
- 変数 set_val を temp_val++ の値として宣言し、first = set_val * (size / 4)、end = (set_val + 1) * (size / 4) - 1、mid_val = first + (end - first) / 2 を求めます。
- first < end である場合、Sorting_Threading(first, mid_val)、Sorting_Threading(mid_val + 1, end)、combine_array(first, mid_val, end) を呼び出します。
関数 void Sorting_Threading(int first, int end) の内部
- 変数 mid_val を first + (end - first) / 2 として宣言します。
- first < end である場合、再帰的に Sorting_Threading(first, mid_val)、Sorting_Threading(mid_val + 1, end)、combine_array(first, mid_val, end) を呼び出します。
関数 void combine_array(int first, int mid_val, int end) の内部
- int* start = new int[mid_val - first + 1]、int* last = new int[end - mid_val]、temp_1 = mid_val - first + 1、temp_2 = end - mid_val、i、j、k = first を宣言します。
- i = 0 から i < temp_1 までのループで start[i] = arr[i + first] を設定します。
- i = 0 から i < temp_2 までのループで last[i] = arr[i + mid_val + 1] を設定します。
- i と j を 0 に初期化し、i < temp_1 かつ j < temp_2 の間ループします。start[i] <= last[j] なら arr[k++] = start[i++]、そうでなければ arr[k++] = last[j++] とします。
- 残りの要素についても、i < temp_1 の間は arr[k++] = start[i++]、j < temp_2 の間は arr[k++] = last[j++] としてコピーします。
サンプルコード
#include <iostream>
#include <pthread.h>
#include <time.h>
#define size 20
#define thread_size 4
using namespace std;
int arr[size];
int temp_val = 0;
void combine_array(int first, int mid_val, int end){
int* start = new int[mid_val - first + 1];
int* last = new int[end - mid_val];
int temp_1 = mid_val - first + 1;
int temp_2 = end - mid_val;
int i, j;
int k = first;
for(i = 0; i < temp_1; i++){
start[i] = arr[i + first];
}
for (i = 0; i < temp_2; i++){
last[i] = arr[i + mid_val + 1];
}
i = j = 0;
while(i < temp_1 && j < temp_2){
if(start[i] <= last[j]){
arr[k++] = start[i++];
}
else{
arr[k++] = last[j++];
}
}
while (i < temp_1){
arr[k++] = start[i++];
}
while (j < temp_2){
arr[k++] = last[j++];
}
}
void Sorting_Threading(int first, int end){
int mid_val = first + (end - first) / 2;
if(first < end){
Sorting_Threading(first, mid_val);
Sorting_Threading(mid_val + 1, end);
combine_array(first, mid_val, end);
}
}
void* Sorting_Threading(void* arg){
int set_val = temp_val++;
int first = set_val * (size / 4);
int end = (set_val + 1) * (size / 4) - 1;
int mid_val = first + (end - first) / 2;
if (first < end){
Sorting_Threading(first, mid_val);
Sorting_Threading(mid_val + 1, end);
combine_array(first, mid_val, end);
}
}
int main(){
for(int i = 0; i < size; i++){
arr[i] = rand() % 100;
}
pthread_t P_TH[thread_size];
for(int i = 0; i < thread_size; i++){
pthread_create(&P_TH[i], NULL, Sorting_Threading, (void*)NULL);
}
for(int i = 0; i < 4; i++){
pthread_join(P_TH[i], NULL);
}
combine_array(0, (size / 2 - 1) / 2, size / 2 - 1);
combine_array(size / 2, size/2 + (size-1-size/2)/2, size - 1);
combine_array(0, (size - 1)/2, size - 1);
cout<<"Merge Sort using Multi-threading: ";
for (int i = 0; i < size; i++){
cout << arr[i] << " ";
}
return 0;
}出力結果
上記のコードを実行すると、次のような出力が得られます。
Merge Sort using Multi-threading: 15 21 26 26 27 35 36 40 49 59 62 63 72 77 83 86 86 90 92 93
このように、POSIXスレッド(pthread)を利用して配列を4つのブロックに分割し、各スレッドが担当範囲を独立してマージソートすることで、大規模なデータの並べ替えを効率化できます。最後にメインスレッドがすべての部分配列を統合し、完全にソートされた配列が完成します。
-
C言語でマージソートを使って配列をソートするプログラムの作成方法
配列(アレイ)とは、共通の名前を共有する関連性のあるデータ項目の集まりです。配列内の特定の値は、「添字(インデックス番号)」によって識別されます。 配列の宣言 配列を宣言するための構文は、次のとおりです。 datatype array_name [size]; たとえば、次のように宣言します。 float marks [50]; この宣言により、「marks」はfloat型の要素を50個格納できる配列として定義されます。 int number[10]; この宣言により、「number」は整数定数を最大10個まで格納できる配列として定義されます。 配列の各要素は「配列インデックス(添字)」を使
-
マージソートを使って配列の転倒数(反転数)を数えるC/C++プログラム
転倒数(Inversion Count)とは?与えられた配列をソートする際に発生する反転(転倒)の回数を「転倒数(Inversion Count)」と呼びます。転倒数を求める問題は古典的なアルゴリズム問題の一つで、マージソート(Merge Sort)のアルゴリズムを応用することで効率的に解くことができます。この問題では、各要素について「自分より左側にあり、かつ自分より大きな値を持つ要素」の数をすべて数え上げ、その合計を出力します。この処理は、マージソートのマージ(merge)関数の中で実装されます。理解を深めるために、マージ処理で扱う2つの部分配列を例に考えてみましょう。配列の転倒数の定義配列