要將乙個陣列排序,可以先(遞迴的)將它分成層兩半分別排序,然後將結果歸併起來。
它能保證任意長度為n的陣列排序所需時間和nlogn成正比;它的缺點則是它所需的額外空間和n成正比。
實現歸併的一種直截了當的辦法是將兩個不同的有序陣列歸併到第三個陣列中,兩個陣列中的元素應該都實現了comparable藉口。實現的方法很簡單,建立乙個適當大小的陣列然後將兩個輸入陣列的元素從乙個個從小到大放入這個陣列中。
但是由於遞迴的原因,這種方法效率不高。我們更希望有一種能夠在原地歸併的方法,這樣就可以先將前半部分排序,再將後半部分排序,然後在陣列中移動元素而不需要額外的空間(即不需要第三個陣列了)。
示意圖:
也就是上面那張圖描述的。**:
public
class
merge
// stably merge a[lo .. mid] with a[mid+1 ..hi] using aux[lo .. hi]
private
static
void
merge(comparable a, comparable aux, int lo, int mid, int hi)
// merge back to a
int i = lo, j = mid+1;
for (int k = lo; k <= hi; k++)
// postcondition: a[lo .. hi] is sorted
assert issorted(a, lo, hi);
}private
static
void
merge(comparable a, int index, int aux, int lo, int mid, int hi)
// merge back to a
int i = lo, j = mid+1;
for (int k = lo; k <= hi; k++)
}
// mergesort a[lo..hi] using auxiliary array aux[lo..hi]
private
static
void
sort(comparable a, comparable aux, int lo, int hi)
public
static
void
sort(comparable a)
// is v < w ?
private
static
boolean
less(comparable v, comparable w)
// exchange a[i] and a[j]
private
static
void
exch(object a, int i, int j)
private
static
boolean
issorted(comparable a)
private
static
boolean
issorted(comparable a, int lo, int hi)
public
static
int indexsort(comparable a)
// mergesort a[lo..hi] using auxiliary array aux[lo..hi]
private
static
void
sort(comparable a, int index, int aux, int lo, int hi)
// print array to standard output
private
static
void
show(comparable a)
}public
static
void
main(string args)
}
merge的精髓(也就是排序):左半邊用盡,則取右半邊元素;右半邊用盡,則取左半邊元素;右半邊的當前元素小於左半邊的當前元素,則取右半邊元素;右半邊的當前元素大於左半邊的當前元素,則取左半邊的元素。實際上大部分發生的都是後面兩句話,前面兩句只是特殊情況而已。
改進:
對小規模子陣列採用插入排序:
因為遞迴會使小規模問題中方法的呼叫過於頻繁,所以改進對它們的處理方法就能改進整個演算法。使用插入排序處理小規模的子陣列,一般可以將歸併排序的執行時間雖短10%~15%。無**
測試陣列是否已經有序:可以新增乙個判斷條件,如果a[mid]小於a[mid+1],我們就任務陣列已經是有序的並跳過merge方法(指的是兩個sort後面的merge)。這個改動不影響排序的遞迴呼叫,但是任意有序的子陣列演算法的執行時間就變成線性的了。
不將元素複製到輔助陣列:我們可以節省將陣列複製到用於歸併的輔助陣列所用的時間。要做到這一點我們要呼叫兩種排序方法,一種將資料從輸入陣列排序到輔助陣列,一種將資料從輔助陣列排序到輸入陣列,這種方法需要一些技巧,我們要在遞迴呼叫的每個層次交換輸入陣列和輸出陣列的角色。無**
實現歸併排序的另一種方法是先歸併那些微型陣列,然後再成對歸併得到的陣列,如此這般,直到我們將這個陣列歸併在一起
就是上面的演算法倒過來
public
class
mergebu
// stably merge a[lo..mid] with a[mid+1..hi] using aux[lo..hi]
private
static
void
merge(comparable a, comparable aux, int lo, int mid, int hi)
// merge back to a
int i = lo, j = mid+1;
for (int k = lo; k <= hi; k++)
}public
static
void
sort(comparable a)
}assert issorted(a);
}// is v < w ?
private
static
boolean
less(comparable v, comparable w)
// exchange a[i] and a[j]
private
static
void
exch(object a, int i, int j)
private
static
boolean
issorted(comparable a)
// print array to standard output
private
static
void
show(comparable a)
}public
static
void
main(string args)
}
注意:當陣列長度為2的冪時,自頂向下和自底向上的歸併排序所用的次數和陣列訪問的次數正好相同,只是順序不同。其他時候,兩種方法的比較和陣列的訪問次數會有所不同
歸併排序的侷限性:
歸併排序的空間複雜度不是最優的
在實踐中不一定會遇到最壞情況
除了比較,演算法的其他操作(例如訪問陣列)也可能很重要;
不進行比較也能將某些資料排序
排序演算法(六) 歸併排序
對於歸併排序,與快速排序一樣,巧妙的應用了分治演算法的核心思想,它將整個序列分成若干組子串行,對這些子串行進行排序後,在一步一步進行合併有序子串行,從而使得整個序列達到有序。但針對於歸併排序,有著兩種不同的排序方式,一是通過整個序列進行著手,將這個序列進行一步一步的劃分,自頂向下進行處理,主要步驟包...
排序演算法 六 歸併排序
歸併排序的基本思想是分治法。先將無序序列分為若干個無序子串行,然後對無序子串行進行排序,最後合併有序子串行,得到完全有序的序列。這就是分解,求解,合併的過程。將待排序序列data 0,1,2 n 1 看成是n個長度為一的序列,將相鄰序列進行合併,得到n 2個長度為2的有序序列 將相鄰序列再次進行合併...
排序系列六 歸併排序
這個排序有點費腦子,大家還得花時間好好看看 歸併排序原理如下圖所示 如下 歸併排序 mergesort 建立在歸併操作上的採用分治法的一種有效的排序演算法,將已有序的子串行合併,得到完全有序的序列 即先使每個子串行有序,再使子串行段間有序。若將兩個有序表合併成乙個有序表,稱為二路歸併。一般來說從單個...