歸併排序
歸併排序是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。
歸併過程爲:比較a[i]和a[j]的大小,若a[i]≤a[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素a[j]複製到r[k]中,並令j和k分別加上1,如此循環下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元。歸併排序的算法我們通常用遞歸實現,先把待排序區間[s,t]以中點二分,接着把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸併操作合併成有序的區間[s,t]。
歸併操作
歸併操作(merge),也叫歸併算法,指的是將兩個順序序列合併成一個順序序列的方法。
如 設有數列{6,202,100,301,38,8,1}
初始狀態:6,202,100,301,38,8,1
第一次歸併後:{6,202},{100,301},{8,38},{1},比較次數:3;
第二次歸併後:{6,100,202,301},{1,8,38},比較次數:4;
第三次歸併後:{1,6,8,38,100,202,301},比較次數:4;
總的比較次數爲:3+4+4=11,;
逆序數爲14;
用途
排序
(速度僅次於快速排序,爲穩定排序算法,一般用於對總體無序,但是各子項相對有序的數列,應用見2011年普及複賽第3題“瑞士輪”的標程)
求逆序對數
具體思路是,在歸併的過程中計算每個小區間的逆序對數,進而計算出大區間的逆序對數(也可以用樹狀數組來求解).
對於原始的數組2,1,3,8,5,7,6,4,10,在整個過程執行的是順序是途中紅色編號1-20。雖然我們描述中說的是程序先分解,再歸併,但實際過程是一邊分解一邊歸併,前半部分分先排好序,後半部分再排好,最後整個歸併爲一個完整的序列,途中的merge過程它所在層的兩個序列的merge過程:下圖展示了每個merge過程對作用於數組的哪部分(紅色)。
整個過程就像一個動態的樹,執行順序就是對樹的先序遍歷順序。
C代碼:
#include <stdio.h>
#include <stdlib.h>
void MergeArray(int a[], int temp[] , int start , int middle , int end)
{
int i,j,s,m,e;
i=start;
s = start ;
m = middle+1 ;
e = end ;
while((s <= middle)&&(m <= end))//因爲包含開始和結束字符,所以用 <=
{
if(a[s] < a[m])
{
temp[i++] = a[s++] ;
}
else
{
temp[i++] = a[m++] ;
}
}
while(s <= middle)
{
temp[i++] = a[s++] ;
}
while(m <= end)
{
temp[i++] = a[m++] ;
}
for(j = start ; j <= end ; j++)
{
a[j] = temp[j];
}
}
void MergeSort(int a[],int temp[],int start ,int end)
{
int middle ;
if(start < end)
{
middle = (start + end)/2 ;
MergeSort(a , temp , start , middle);//通過遞歸層層劃分,使左邊有序
MergeSort(a , temp , middle+1 , end);//右邊有序
MergeArray(a, temp , start , middle , end);//序列合併
}
}
int main(int argc, char *argv[])
{
int a[9] = {2,1,3,8,5,7,6,4,10};
int temp[9] ;
int i;
MergeSort(a , temp , 0 ,8);
for(i = 0 ; i <= 8 ; i ++)
{
printf("%d ",a[i]);
}
return 0;
}