誰かが英語で非再帰的マージソートがどのように機能するのか説明できますか?
ありがとう
要素をループし、必要に応じて2つを交換することで、隣接する2つすべてのグループを並べ替えます。
ここで、2つのグループのグループ(任意の2つ、最も可能性の高い隣接グループですが、最初と最後のグループを使用できます)を処理して1つのグループにマージし、4つの要素すべてがグループ4になりました。これで、4のグループと残りの可能性のあるグループしかなくなりました。前のロジックの周りのループを使用して、今回は4のグループで機能することを除いて、もう一度繰り返します。このループは、グループが1つになるまで実行されます。
非再帰的マージソートは、入力配列に対してウィンドウサイズ1,2,4,8,16..2 ^ nを考慮することで機能します。各ウィンドウ(以下のコードでは「k」)では、隣接するすべてのウィンドウのペアが一時スペースにマージされ、配列に戻されます。
これが私の単一の関数、Cベースの非再帰マージソートです。入力と出力は「a」にあります。 'b'の一時ストレージ。ある日、適切なバージョンが欲しいのですが。
float a[50000000],b[50000000];
void mergesort (long num)
{
int rght, wid, rend;
int i,j,m,t;
for (int k=1; k < num; k *= 2 ) {
for (int left=0; left+k < num; left += k*2 ) {
rght = left + k;
rend = rght + k;
if (rend > num) rend = num;
m = left; i = left; j = rght;
while (i < rght && j < rend) {
if (a[i] <= a[j]) {
b[m] = a[i]; i++;
} else {
b[m] = a[j]; j++;
}
m++;
}
while (i < rght) {
b[m]=a[i];
i++; m++;
}
while (j < rend) {
b[m]=a[j];
j++; m++;
}
for (m=left; m < rend; m++) {
a[m] = b[m];
}
}
}
}
ちなみに、これがO(n log n)であることを証明することも非常に簡単です。ウィンドウサイズの外側のループは2の累乗で大きくなるため、kの繰り返しはlog nです。内部ループでカバーされるウィンドウは多数ありますが、指定されたkのすべてのウィンドウは入力配列を正確にカバーするため、内部ループはO(n)です。内部ループと外部ループを組み合わせる:O(n)* O(log n)= O(n log n)。
Algorithmist からの引用:
ボトムアップマージソートは、マージソートの非再帰的な変形であり、配列は一連のパスによってソートされます。各パスの間、配列はサイズmのブロックに分割されます。 (最初はm = 1)。隣接する2つのブロックはすべてマージされ(通常のマージソートの場合と同様)、次のパスはmの2倍の値で行われます。
非再帰的なMergeSortを使用する主な理由は、再帰スタックオーバーフローを回避するためです。たとえば、1億のレコードをソートしようとしています。各レコードの長さは約1 kByte(= 100ギガバイト)で、英数字順にソートします。 order(N ^ 2)の並べ替えには10 ^ 16の演算が必要です。つまり、比較演算ごとに0.1マイクロ秒でも実行するには数十年かかります。注文(N log(N))マージソートは、同じ操作速度で実行するために10 ^ 10未満の操作または1時間未満かかります。ただし、MergeSortの再帰バージョンでは、1億の要素の並べ替えにより、MergeSort()が5,000万回再帰的に呼び出されます。スタックの再帰あたり数百バイトで、プロセスがヒープメモリ内に簡単に収まる場合でも、再帰スタックがオーバーフローします。ヒープに動的に割り当てられたメモリを使用してマージソートを実行します-上記のRama Hoetzleinによって提供されたコードを使用していますが、スタックを使用する代わりにヒープに動的に割り当てられたメモリを使用しています-1億レコードを非再帰的なマージソートであり、スタックをオーバーフローさせません。ウェブサイト「Stack Overflow」にふさわしい会話!
PS:コードをありがとう、Rama Hoetzlein。
PPS:ヒープで100ギガバイト?まあ、それはHadoopクラスターの仮想ヒープであり、MergeSortは、負荷を共有する複数のマシンに並行して実装されます...
再帰的マージソートと非再帰的マージソートはどちらも、O(nlog(n))と同じ時間の複雑さを持っています。これは、両方のアプローチがスタックをどちらか一方の方法で使用するためです。
非再帰的アプローチでは、ユーザー/プログラマーがスタックを定義して使用します
再帰的アプローチでは、スタックはシステムによって内部的に使用され、再帰的に呼び出される関数の戻りアドレスを格納します
私はここでは新人です。 Rama Hoetzleinソリューションを変更しました(アイデアをありがとう)。私のマージソートは最後のコピーバックループを使用していません。さらに、挿入ソートにフォールバックします。私は自分のラップトップでそれをベンチマークしました、そしてそれは最速です。再帰バージョンよりも優れています。ちなみに、それはJavaであり、降順から昇順でソートされます。もちろん、それは反復的です。マルチスレッドにすることができます。コードは複雑になっています。したがって、興味がある人は、見てください。
コード:
int num = input_array.length;
int left = 0;
int right;
int temp;
int LIMIT = 16;
if (num <= LIMIT)
{
// Single Insertion Sort
right = 1;
while(right < num)
{
temp = input_array[right];
while(( left > (-1) ) && ( input_array[left] > temp ))
{
input_array[left+1] = input_array[left--];
}
input_array[left+1] = temp;
left = right;
right++;
}
}
else
{
int i;
int j;
//Fragmented Insertion Sort
right = LIMIT;
while (right <= num)
{
i = left + 1;
j = left;
while (i < right)
{
temp = input_array[i];
while(( j >= left ) && ( input_array[j] > temp ))
{
input_array[j+1] = input_array[j--];
}
input_array[j+1] = temp;
j = i;
i++;
}
left = right;
right = right + LIMIT;
}
// Remainder Insertion Sort
i = left + 1;
j = left;
while(i < num)
{
temp = input_array[i];
while(( j >= left ) && ( input_array[j] > temp ))
{
input_array[j+1] = input_array[j--];
}
input_array[j+1] = temp;
j = i;
i++;
}
// Rama Hoetzlein method
int[] temp_array = new int[num];
int[] swap;
int k = LIMIT;
while (k < num)
{
left = 0;
i = k;// The mid point
right = k << 1;
while (i < num)
{
if (right > num)
{
right = num;
}
temp = left;
j = i;
while ((left < i) && (j < right))
{
if (input_array[left] <= input_array[j])
{
temp_array[temp++] = input_array[left++];
}
else
{
temp_array[temp++] = input_array[j++];
}
}
while (left < i)
{
temp_array[temp++] = input_array[left++];
}
while (j < right)
{
temp_array[temp++] = input_array[j++];
}
// Do not copy back the elements to input_array
left = right;
i = left + k;
right = i + k;
}
// Instead of copying back in previous loop, copy remaining elements to temp_array, then swap the array pointers
while (left < num)
{
temp_array[left] = input_array[left++];
}
swap = input_array;
input_array = temp_array;
temp_array = swap;
k <<= 1;
}
}
return input_array;
誰かがこのスレッドにまだ潜んでいる場合に備えて...私は上記のラマヘッツラインの非再帰マージソートアルゴリズムを適合させて、二重リンクリストをソートしました。この新しい並べ替えはインプレースで安定しており、他のリンクリストのマージ並べ替えの実装にある時間のかかるリスト分割コードを回避します。
// MergeSort.cpp
// Angus Johnson 2017
// License: Public Domain
#include "io.h"
#include "time.h"
#include "stdlib.h"
struct Node {
int data;
Node *next;
Node *prev;
Node *jump;
};
inline void Move2Before1(Node *n1, Node *n2)
{
Node *prev, *next;
//extricate n2 from linked-list ...
prev = n2->prev;
next = n2->next;
prev->next = next; //nb: prev is always assigned
if (next) next->prev = prev;
//insert n2 back into list ...
prev = n1->prev;
if (prev) prev->next = n2;
n1->prev = n2;
n2->prev = prev;
n2->next = n1;
}
void MergeSort(Node *&nodes)
{
Node *first, *second, *base, *tmp, *prev_base;
if (!nodes || !nodes->next) return;
int mul = 1;
for (;;) {
first = nodes;
prev_base = NULL;
//sort each successive mul group of nodes ...
while (first) {
if (mul == 1) {
second = first->next;
if (!second) {
first->jump = NULL;
break;
}
first->jump = second->next;
}
else
{
second = first->jump;
if (!second) break;
first->jump = second->jump;
}
base = first;
int cnt1 = mul, cnt2 = mul;
//the following 'if' condition marginally improves performance
//in an unsorted list but very significantly improves
//performance when the list is mostly sorted ...
if (second->data < second->prev->data)
while (cnt1 && cnt2) {
if (second->data < first->data) {
if (first == base) {
if (prev_base) prev_base->jump = second;
base = second;
base->jump = first->jump;
if (first == nodes) nodes = second;
}
tmp = second->next;
Move2Before1(first, second);
second = tmp;
if (!second) { first = NULL; break; }
--cnt2;
}
else
{
first = first->next;
--cnt1;
}
} //while (cnt1 && cnt2)
first = base->jump;
prev_base = base;
} //while (first)
if (!nodes->jump) break;
else mul <<= 1;
} //for (;;)
}
void InsertNewNode(Node *&head, int data)
{
Node *tmp = new Node;
tmp->data = data;
tmp->next = NULL;
tmp->prev = NULL;
tmp->jump = NULL;
if (head) {
tmp->next = head;
head->prev = tmp;
head = tmp;
}
else head = tmp;
}
void ClearNodes(Node *head)
{
if (!head) return;
while (head) {
Node *tmp = head;
head = head->next;
delete tmp;
}
}
int main()
{
srand(time(NULL));
Node *nodes = NULL, *n;
const int len = 1000000; //1 million nodes
for (int i = 0; i < len; i++)
InsertNewNode(nodes, Rand() >> 4);
clock_t t = clock();
MergeSort(nodes); //~1/2 sec for 1 mill. nodes on Pentium i7.
t = clock() - t;
printf("Sort time: %d msec\n\n", t * 1000 / CLOCKS_PER_SEC);
n = nodes;
while (n)
{
if (n->prev && n->data < n->prev->data) {
printf("oops! sorting's broken\n");
break;
}
n = n->next;
}
ClearNodes(nodes);
printf("All done!\n\n");
getchar();
return 0;
}
2017-10-27を編集:奇数番号リストに影響するバグを修正