分治法 合併排序

2021-09-25 23:15:23 字數 3944 閱讀 8241

排序演算法是對一組數進行順序排序或者逆序排序,而合併排序就是排序演算法的一種。合併排序用到了分治策略實現對元素進行排序。

合併排序的基本思想:把待排序的n個元素分解成n組,也就是每組乙個元素;之後對分好的組進行兩兩合併(無配對的則不操作),以此類推。

以序列為例,排序過程如下:

**合併排序又叫做2-路歸併排序,是因為它每次都是兩兩歸併。

/**

* 合併src[left:mid] src[mid+1:right] 到dest[left:right]

* @param src: 源陣列

* @param dest: 目的陣列

* @param left: 陣列起始索引

* @param mid: 源陣列的中間索引

* @param right: 目的陣列的結束索引

*/templatevoid merge(type src, type dest, int left, int mid, int right)

//把剩下的陣列元素拷貝到目的陣列

if (i > mid)

for (int q = j; q <= right; q++)

dest[k++] = src[q];

else

for (int q = i; q <= mid; q++)

dest[k++] = src[q];

}

merge函式用於把src[left:mid] 和src[mid + 1: right]歸併到dest的對應位置。其思路如下

設定兩個索引變數i和j儲存當前指向的src[left:mid] 和src[mid + 1: right]的索引;

每次從i 和j 所指向的src[i] src[j]中取乙個小值依次放到dest的對應位置,然後這個索引加一;

重複2步驟直至遍歷完短陣列;

把剩餘的值拼接到dest陣列中。

/**

* src[left:right]複製到dest[left:right]

* @param src: 源陣列

* @param dest: 目的陣列

* @param left: 起始索引

* @param right: 結束索引

*/templatevoid copy(type src, type dest, int left, int right)

}

copy的作用就是陣列複製。

/**

* 用於合併排序好的相鄰陣列段

*/templatevoid mergepass(type x, type y, int step, int n)

if (i + step < n)

merge(x, y, i, i + step - 1, n - 1);

else

for (int j = i; j < n ; j++)

y[j] = x[j];

}

mergepass函式主要用於合併排序好的相鄰陣列,

當step為1,時,此時陣列被分成了n份,mergepass的while就是把上面的單個元素兩兩合併;

當step為2,時,則對最多為2個元素的子陣列進行兩兩合併。

以此類推

在之後還有乙個判斷:i + step < n,這裡舉例子來說明:

比如此時進行到了第二次歸併,即step = 2, 元素如下:

x = ,

i = 0時,合併的是x[0: 1]和x[2:3],之後i = 0 + 2 * 2 = 4;

i = 4,時,得到i <= 7 - 2 * 2 = 3 不成立,所以不再迴圈。

雖然迴圈不成立,但是可以看到, 還是滿足i + step < n的,所以這裡又加了最後乙個歸併。

而當x = ,step=2的情況下則會直接進行複製。

從上面的舉例可以看出,賦予step遞增的值,則可以實現歸併排序。

templatevoid mergesort(type a, int n) 

delete temp;

}

在mergesort函式中,呼叫mergepass來實現歸併操作。

接著測試一下:

int main() ;

int len = sizeof(a) / sizeof(a[0]);

//合併排序

mergesort(a, len);

for (int i = 0; i < len; i++)

cout << a[i] << " ";

cout << endl;

return 0;

}

自然合併排序是上述所說的合併排序演算法的乙個變形。之前說過,合併排序是從乙個元素開始兩兩歸併的,而當乙個組內只有乙個元素時,能夠保證它是排序好的。基於上面的原理,給定乙個陣列,一般都會存在多個長度大於1的已自然排好序的子陣列段。比如對於乙個陣列。自然排序的子陣列段為 ;第一次排序可以得到 ;第二次排序得到 ;第三次排序則可以得到遞增序列。

上面需要解決的問題就是如何儲存每次的子陣列段的開始和結束,因為子陣列段的大小不是固定的,所以還需要乙個陣列去維護子陣列的邊界。

templatevoid mergesort(type a, int len) 

//回寫到陣列a

for (int i = 0; i < len; i++)

a[i] = temp[i];

number = get_order(a, len, indices);

} delete temp;

}

新的mergesort函式用到了子陣列段,其中維護了indices陣列來儲存邊界,

首先可以看到indices的大小為len + 1,這是最壞情況下的indices的最大值,當陣列完全逆序的時候,比如要把排序成時,我們可以一眼看出只需要完全翻轉就可以了,不過程式則不知道,按照之前的步驟來說,以上分成了 共5個,再加上一開始的-1,所以最大陣列長度設為了len + 1。

第乙個元素為-1,主要是為了應對之後的排序。以為例子。自然排序的子陣列段為 ;那麼indices的值為。那麼需要a[-1 + 1: 1]和a[1 + 1, 4]進行歸併,a[4 +1: 5]和a[5 + 1:6]歸併...

每一次的迴圈都會重新獲取排序好的子陣列段(其實可以從之前得到的)。

/**

* 獲取陣列a的自然排好序的子陣列段,返回結束索引

* @param a: 陣列

* @param len: 陣列a的長度

* @param indices: 排序好的結束索引陣列 需要確保長度足夠

* @return: 返回indices真正的長度

*/templateint get_order(type a, int len, vector& indices)

index++;

} indices[i++] = len - 1;

return i;

}

根據mergesort和get_order而得到的indices陣列來說,必定存在的就是-1和最後乙個元素的索引,所以當indices的實際個數為2的時候,表示排序結束。

本測試在leetcode上執行,題目為:排序陣列

普通歸併排序執行:

自然歸併排序執行:

可以看到,自然歸併排序的執行用時要小於普通歸併排序,不過記憶體消耗略微增加。

分治法 合併排序

合併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用 分治法 divide and conquer 的乙個非常典型的應用。合併排序法是將兩個 或兩個以上 有序表合併成乙個新的有序表,即把待排序序列分為若干個子串行,每個子串行是有序的。然後再把有序子串行合併為整體有序序列。將已有序的子串行...

分治法合併排序(C )

參考 include include include using namespace std 合併函式 void merge int arr,int p,int q,int r for int j 0 j len2 j l len1 r len2 int max 定義無窮大 int i 0,j 0 ...

基礎演算法 合併排序(分治法)

基礎演算法 合併排序演算法 分治法 的宗旨是將問題 分解 處理 歸併 將書中的偽 翻譯為c c 語言實現,大致可用兩個函式來解決問題,第乙個函式實現 治 也就是分解問題,處理問題 的步驟,另乙個函式通 過遞迴實現 分 合 的操作。實現 分 治 的函式實現如下 void merge int parra...