Mergesortでの最悪のケースはO(nlogn)であり、平均的なケースと同じであることを知っています。
ただし、データが昇順または降順の場合、これは最小比較数になるため、mergesortはランダムデータよりも高速になります。だから私の質問は次のとおりです:どの種類の入力データが最大比較数を生成し、マージソートが遅くなりますか?
this 質問の答え:
一部のソートアルゴリズム(クイックソートなど)では、要素の初期順序が実行する操作の数に影響する場合があります。ただし、とにかくまったく同じ数の操作を行う必要があるため、mergesortには変更を加えません。再帰的に小さな配列に分割し、合計Θ(nlogn)時間でそれらをマージします。
しかし、これは間違っています。この時点で、2つのサブ配列があり、初期データがソートされている場合にそれらをマージしたいので、n/2回の比較しかできません。これは、2番目の配列の最初の要素を持つonlyを持つ最初のサブ配列のすべての要素です。しかし、それ以上のことを達成できます。その入力データを探しています。
マージソートの最悪のケースは、マージソートが実行する必要があるケースです。最大比較数
そこで、最悪の場合をボトムアップで構築してみます。
ソート後の最終ステップの配列が{0,1,2,3,4,5,6,7}
であるとします
最悪の場合、このステップの前の配列は{0,2,4,6,1,3,5,7}
でなければなりません。ここで、左のsubarray = {0,2,4,6}
と右のsubarray = {1,3,5,7}
は最大の比較になります。(左右のサブアレイの要素)
理由:配列のすべての要素が少なくとも一度比較されます。
前の手順の左右のサブアレイに上記の同じロジックを適用します。配列{0,2,4,6}
の場合、最悪のケースは、前の配列が{0,4}
および{2,6}
であり、配列{1,3,5,7}
である場合です。最悪の場合は、{1,5}
および{3,7}
になります。
{0,4}
は{4,0}
でなければならず、{2,6}
は{6,2}
でなければなりません、{1,5}
は{5,1}
である必要があります{3,7}
は{7,3}
である必要がありますよく見ると、このステップは不要です。なぜなら、set/arrayのサイズが2の場合、サイズ2の配列がソートされていても、すべての要素が少なくとも一度比較されるからです。Applying Merge Sort using Divide and Conquer
Input array arr[] = [4,0,6,2,5,1,7,3]
/ \
/ \
[4,0,6,2] and [5,1,7,3]
/ \ / \
/ \ / \
[4,0] [6,2] [5,1] [7,3] Every pair of 2 will be compared atleast once therefore maximum comparison here
| | | |
| | | |
[0,4] [2,6] [1,5] [3,7] Maximum Comparison:Every pair of set is used in comparison
\ / \ /
\ / \ /
[0,2,4,6] [1,3,5,7] Maximum comparison again: Every pair of set compared
\ /
\ /
[0,1,2,3,4,5,6,7]
サイズnの配列に同じロジックを適用できるようになりました
以下は、上記のロジックを実装するプログラムです。
注:以下のプログラムは、2のべき乗に対してのみ有効ではありません。サイズnの配列に対して最悪のケースを提供する一般的な方法です。自分で入力用に異なる配列を試すことができます。
class MergeWorstCase
{
public static void print(int arr[])
{
System.out.println();
for(int i=0;i<arr.length;i++)
System.out.print(arr[i]+" ");
System.out.println();
}
public static void merge(int[] arr, int[] left, int[] right) {
int i,j;
for(i=0;i<left.length;i++)
arr[i]=left[i];
for(j=0;j<right.length;j++,i++)
arr[i]=right[j];
}
//Pass a sorted array here
public static void seperate(int[] arr) {
if(arr.length<=1)
return;
if(arr.length==2)
{
int swap=arr[0];
arr[0]=arr[1];
arr[1]=swap;
return;
}
int i,j;
int m = (arr.length + 1) / 2;
int left[] = new int[m];
int right[] = new int[arr.length-m];
for(i=0,j=0;i<arr.length;i=i+2,j++) //Storing alternate elements in left subarray
left[j]=arr[i];
for(i=1,j=0;i<arr.length;i=i+2,j++) //Storing alternate elements in right subarray
right[j]=arr[i];
seperate(left);
seperate(right);
merge(arr, left, right);
}
public static void main(String args[])
{
int arr1[]={0,1,2,3,4,5,6,7};
seperate(arr1);
System.out.print("For array 1:");
print(arr1);
int arr2[]={0,1,2,3,4,5,6,7,8};
seperate(arr2);
System.out.print("For array 2:");
print(arr2);
}
}
出力:
For array 1:
4 0 6 2 5 1 7 3
For array 2:
8 0 4 6 2 5 1 7 3
私の教授の一人がくれたきちんとしたアルゴリズムは、反対のアプローチを使用してこれを解決します。初期配列をより小さなブロックに分割するのではなく、ベースケースから始めて再帰的なパターンに従うことができます。
基本ケースは[1]と[2、1]で、サイズが1
と2
の最悪の場合の配列の例です。それから、次のように3
および4
の配列を構築します。
n
とm
の2つの配列を取ります。たとえば、n + m = x
(xは探しているサイズ)このアルゴリズムを使用して、サイズ3
および4
の配列の一連の手順を次に示します。
3
[1] + [2, 1]
を取る[1 | 2, 1]
を取得します[2 | 2, 1]
[2 | 3, 1] -> [2, 3, 1]
4
[2, 1] + [2, 1]
を取る[2, 1 | 2, 1]
を取得します[4, 2 | 2, 1]
[4, 2 | 3, 1] -> [4, 2, 3, 1]
7
[2, 3, 1] + [4, 2, 3, 1]
を取る[2, 3, 1 | 4, 2, 3, 1]
を取得します[4, 6, 2 | 4, 2, 3, 1]
[4, 6, 2 | 7, 3, 5, 1] -> [4, 6, 2, 7, 3, 5, 1]
このアプローチを採用し、巨大なアレイサイズに簡単に構築できる方法を簡単に確認できます。
このアルゴリズムを実装するpython関数です。
import math
def worstCaseArrayOfSize(n):
if n == 1:
return [1]
else:
top = worstCaseArrayOfSize(int(math.floor(float(n) / 2)))
bottom = worstCaseArrayOfSize(int(math.ceil(float(n) / 2)))
return map(lambda x: x * 2, top) + map(lambda x: x * 2 - 1, bottom)