改進的二路歸併排序(演算法4)
阿新 • • 發佈:2018-12-09
文章目錄
簡述
我們常見的二路歸併排序基本上分為兩步,時間複雜度為 ,空間複雜度為 。下面是我們常用的寫法:
void merge(int *data, int low, int mid, int high)
{
int *tmp = new int[high + 1];
int i = low;
int j = mid + 1;
for (int k = low; k <= high; k++)
{
tmp[k] = data[k];
}
int k = low;
for (k = low; i <= mid && j <= high;)
{
if (tmp[i] < tmp[ j])
{
data[k++] = tmp[i++];
}
else
{
data[k++] = tmp[j++];
}
}
while (i <= mid && k <= high)
{
data[k++] = tmp[i++];
}
while (j <= high && k <= high)
{
data[k++] = tmp[j++];
}
delete tmp;
}
void sort_merge(int *data, int low, int high)
{
if (low < high)
{
int mid = (low + high) / 2;
sort_merge(data, low, mid);
sort_merge(data, mid + 1, high);
merge(data, low, mid, high);
}
}
但是我們可以看到,從始至終我們都對每個子陣列進行歸併,但是我們知道對於小規模的陣列一般的插入排序,選擇排序或者希爾排序效能表現得更好,因此在小規模的資料上沒有必要進行二路歸併。第二,每次在merge中進行資料的拷貝本身就很大消耗。第三,無論陣列是否有序我們都會二路歸併到底,這對於整個演算法來說不太好。因此,對應的改進辦法如下。
改進辦法
- 利用data[mid] < data[mid + 1]進行有序性判斷。因為經過歸併我們可以保證左右兩個子陣列都是有序的,只要第一個子陣列的最後一個元素小於第二個字陣列的第一個元素陣列一定是有序的;
- 對小規模的資料使用簡單的線性排序演算法,比如希爾排序。這裡選擇希爾,選擇排序,插入排序都可以(ps:冒泡不太建議,但是有興趣的可以試下)。
- 在每次歸併後交換輔助陣列和資料陣列的角色。也就是在merge中不需要將子陣列的值複製回資料陣列,直接將資料陣列排序進輔助陣列,在執行結束後交換二者的角色即可。順便說下,我這個沒有實現,我覺得作者的意思是使用系統呼叫類似於memcpy一次性複製元素來提高每次merge時單個單個複製的效能。
實現程式碼,我這裡使用的是希爾排序進行小規模排序:
void merge_shell(int *data, int low, int high)
{
int gap = 1;
while (gap < (high - low + 1) / 3) gap = 3 * gap + 1;
while (gap >= 1)
{
for (int i = gap + low; i <= high; i++)
{
for (int j = i; j >= (gap + low) && data[j] < data[j - gap]; j -= gap)
{
swap(&data[j], &data[j - gap]);
}
}
gap = gap / 3;
}
}
void merge(int *data, int *tmp, int low, int mid, int high)
{
int i = low;
int j = mid + 1;
int k = low;
for (k = low; i <= mid && j <= high;)
{
if (data[i] < data[j])
{
tmp[k++] = data[i++];
}
else
{
tmp[k++] = data[j++];
}
}
while (i <= mid && k <= high)
{
tmp[k++] = data[i++];
}
while (j <= high && k <= high)
{
tmp[k++] = data[j++];
}
for (int i = low; i <= high; i++)
{
data[i] = tmp[i];
}
}
void merge_sort(int *data, int *tmp, int low, int high)
{
if (low < high)
{
if ((high - low + 1) <= 10)
{
merge_shell(data, low, high);
}
else
{
int mid = (low + high) / 2;
merge_sort(data, tmp, low, mid);
merge_sort(data, tmp, mid + 1, high);
if (data[mid] < data[mid + 1]) //the array has been sorted, we do not need to sort again
{
return;
}
merge(data, tmp, low, mid, high);
}
}
}