基礎演算法排序之分治排序

2021-06-15 04:01:48 字數 2731 閱讀 4485

寫在之前的話:

對於博文的內容出現的本人觀點(博文中的內容有的摘自於演算法導論)的不當或者錯誤而對你造成困擾的話,你可以盡情的鄙視與吐槽,最好寫出你的觀點,本人定當虛心受教。

分而治之,顧名思義也就是將原問題的規模分解成一系列規模小的子問題,演算法導論中是這麼說的:

分治模式在每一層遞迴上都有三個步驟:

分解: 將原問題分解成一系列子問題;

解決: 遞迴地解決各子問題。若子問題足夠小,則直接求解

合併: 將子問題的結果合併成原問題的解

1.合併排序

合併排序完全依照了上述模式,直觀的操作如下:  

分解:將n個元素分成含n/2個元素的子串行

解決:用合併排序法對兩個子串行遞迴的排序

合併:合併兩個已排序的子串行以得到排序結果

合併排序的關鍵步驟在於對於兩個已經排好的陣列進行合併,當然如同前面的增量式排序,這邊的兩個已經排好的陣列指的也是通過陣列的序號進行分割的子串行,下面就稱子串行,而合併的演算法也很簡單,將兩個已經排好的子串行分別用兩個臨時陣列a,b儲存,然後將a陣列中的元素與b陣列中的元素逐個比較,比較小的就放進原陣列中,這樣就將兩個子串行就可以合併成乙個排好序的序列.

void merge(int a, int start1, int end1, int end2) 

i = 0;

for (; i < r_length; i++)

i = 0;

while (i < length && k < r_length && j < l_length) else

i ++;

} for (; j < l_length; j++)

for (; k < r_length; k++)

free(l_array);

free(r_array);

}

合併的問題解決了,現在說分解和解決,如果了解樹的後序遍歷的話分解的話就很容易想通了,二叉樹的每一棵子樹對應於合併排序二分後分成的兩個序列,整個的分解與合併其實就是一棵二叉樹後序遍歷的過程,不同的是二叉樹的遍歷只是訪問節點,而合併排序需要的是將左子樹和右子樹合併成乙個新的序列並作為左右子樹的父節點。這樣到最後根節點的時候左右序列就合併成乙個排序好的陣列了。

}2.快速排序

快速排序也是一種分治策略下的排序演算法。按照模式直觀的操作如下:

分解:陣列a[p..r]被劃分成兩個子串行a[p..q-1]和a[q+1..r],使得a[p..q-1]中的每個元素都小於等於a[q],而且,小於等於a[q+1..r]中的元素。下標q也在這個劃分過程中進行計算。

解決:通過遞迴呼叫快速排序,對子陣列a[p..q-1]和a[q+1..r]

合併:因為兩個子陣列是就地排序的,將它們的合併不許要操作:整個陣列a[p..r]已排序

快排的關鍵步驟在於分解,在分解的過程中將乙個陣列分成兩個部分,如果按照非遞減排序,每一次遞迴過程的子串行中,前半部分都是小於等於後半部分,而分割的分割位置就在前半部分與後半部分的中間位置。要對陣列做這樣的乙個過程,還是比較容易的,但在快速排序中採用的是就地排序,不借助於任何臨時儲存,陣列元素的移動只在原陣列中完成。分解的演算法如下:

每一次迭代,將乙個序列當作是四個子串行組成,分別是上述的第一部分,第二部分,以及未成為以上兩部分的第三部分還有就是中間元素,挺繞人的看圖吧

在演算法執行之前,先取序列的最後乙個元素為中間值x,第三部分圖中定義為unrestricted,表示還未確定屬於第一部分還是第二部分的所有元素,演算法結束後第三部分所有的元素都會被加入到第一部分或者第二部分中。陣列的範圍是a[p..r],r表示中間元素x的索引。假設定義第三部分的初始範圍用變數表示是[j..r-1](j=p).用變數i來確定第一部分與第二部分的分割,a[i]是屬於第一部分的。當初始時第一部分的陣列是為空的,那就使得i=p-1,因為如果i=p的話第乙個元素就變成了第一部分了。所有的部分範圍用變數表示為:

第一部分 [p..i]

第二部分 [i+1..j-1]

第三部分 [j..r-1]

第四部分 [r]

演算法過程也就相對簡單了,取第三部分的第乙個元素與中間元素也就是a[r]比較,會有兩種情況:

1. 如果小於或者等於a[r]就將i+1,這樣就等於將第一部分增加了乙個元素

2. 如果大於a[r]就將j+1,其實也就是將第二部分增加了乙個元素

等第三部分沒有了,也就是 j==r-1了,將a[i+1]與a[r]交換,演算法結束。

解決的話就很類似於二叉樹的先序遍歷了,不同於訪問根節點,快排的每次訪問是將左右子樹分成兩個部分,如果是按非遞減排序,左子樹的所有元素小於等於右子樹,並且根節點是中間元素,到完成遍歷後一棵二叉查詢樹(當然二叉查詢樹是不包含相等元素的節點的)也就產生了,這部分就靠自己想象吧!

int partition(int a, int start, int end)

} temp = a[i+1];

a[i+1] = a[end];

a[end] = temp;

return i+1;

}void quick_sort(int a, int p, int r)

}

演算法 之 分治 合併排序 小結

之前我們看過的演算法 bottomupsort 和 mergesort,前者是迭代的,而後者是遞迴的。在這裡我們可以思考一下,既然能夠利用演算法 bottomupsort,為什麼還要借助於像 mergesort 那樣的遞迴演算法呢?尤其是考慮到因使用棧而需要的額外空間數,以及由處理遞迴呼叫內在開銷帶...

演算法篇之分治演算法 歸併排序

分治演算法 分治 把乙個任務分成形式和原任務相同,但規模更小的幾個部分任務 通常是兩個部分 分別完成,或只需要選一部分完成。然後再處理完成後的這乙個或幾個部分的結果,實現整個任務的完成。例題1 歸併排序 陣列排序任務可以如下完成 a.把前一半排序 b.把後一半排序 c.把兩半歸併到乙個新的有序陣列,...

求逆序數之分治排序

求逆序數 時間限制 2000 ms 記憶體限制 65535 kb 難度 5 描述 在乙個排列中,如果一對數的前後位置與大小順序相反,即前面的數大於後面的數,那麼它們就稱為乙個逆序。乙個排列中逆序的總數就稱為這個排列的逆序數。現在,給你乙個n個元素的序列,請你判斷出它的逆序數是多少。比如 1 3 2 ...