演算法系列(四)歸併排序及其改進(java實現)
前言:演算法第四版2.2節 歸併排序學習總結
歸併排序:將兩個有序的陣列歸併成一個更大的有序陣列。採用分治(divide and conquer)策略,利用遞迴每次將陣列分成兩半,直到子陣列個數為1(1個元素的陣列自然就有序的),將結果歸併再返回。
效能:所需時間與NlogN成正比(N為陣列大小);所需額外空間與N成正比。當所有元素都相同時,歸併排序的執行時間是線性的。
歸併兩個有序陣列的merge()方法:
private static void merge(Comparable[] a, int lo, int mid, int hi){
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(aux[j] < aux[i]))
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
先將元素複製到輔助陣列aux[ ]中,四個判斷:左半邊耗盡時,將右版邊剩餘的直接複製到a中;右半邊耗盡時,將左半邊剩餘的直接複製到a中;右半邊元素小於左半邊時,複製右半邊的元素;右半邊的元素大於等於左半邊的元素時,複製左半邊的元素(保證了穩定性)。示例如下:
分治所需時間複雜度的證明:
D (N) = 2 D (N / 2) + N,其中 D( 1 ) = 0,證明D (N) = N lg N
1.圖解法
2.逐步擴充套件法
3.歸納法
自頂向下的歸併排序(標準遞迴):
利用遞迴:一分為二,再一分為二,直到分到1,再return
public class MergeSort {
private static Comparable[] aux; //輔助陣列
public static void sort(Comparable[] a){
aux = new Comparable[a.length];
sort(a,0,a.length-1);
}
private static void sort(Comparable[] a,int lo, int hi){
if(lo>=hi)
return;
int mid = lo + (hi -lo) / 2;
sort(a,lo,mid); //左半排序
sort(a,mid+1,hi); //右半排序
merge(a,lo,mid,hi); //歸併
}
private static void merge(Comparable[] a, int lo, int mid, int hi){
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(less(aux[j],aux[i])) //將aux的元素歸併到a中
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
public static void show(Comparable[] a){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static boolean isSorted(Comparable[] a){
for (int i = 1; i < a.length; i++) {
if(less(a[i], a[i-1]))
return false;
}
return true;
}
}
自底向上的歸併排序:
先歸併小陣列,再成對歸併得到的子陣列,依次往上。size=1,2,4,8,16...,其程式碼量比標準遞迴小。
public static void sort(Comparable[] a){
int N = a.length;
aux = new Comparable[a.length];
for (int size = 1; size < N; size = size+size) {
for(int lo = 0; lo < N-size; lo += size+size){
merge(a, lo, lo+size-1, Math.min(lo+size+size-1, N-1));
}
}
}
不使用靜態陣列來輔助:
由於靜態變數是物件共享的,如果有多個程式同時在用這個類,就會出錯,所以將輔助陣列作為引數傳遞。
public static void sort(Comparable[] a){
Comparable[] aux = new Comparable[a.length];
sort(a,aux,0,a.length-1);
}
private static void sort(Comparable[] a, Comparable[] aux,int lo, int hi){
if(lo>=hi)
return;
int mid = lo + (hi -lo) / 2;
sort(a,aux,lo,mid);
sort(a,aux,mid+1,hi);
merge(a,aux,lo,mid,hi);
}
private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi){
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(less(aux[j],aux[i]))
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
三項改進:
1. 如果子陣列較小(7左右),則採用速度更快的插入排序;
2. 檢測待歸併的兩個子陣列是否已經有序,如果已經有序則直接複製進a[ ];
3. 通過再遞迴中交換引數來避免每次歸併時都要複製陣列到輔助陣列。
private static final int CUTOFF = 7;
public static void sort(Comparable[] a){
// Comparable[] aux = new Comparable[a.length];
Comparable[] aux = a.clone(); //初始化aux,為了後面把它當原始陣列a[]用
sort(aux,a,0,a.length-1);
}
private static void sort(Comparable[] a, Comparable[] aux,int lo, int hi){
int mid = lo + (hi -lo) / 2;
// if(hi <= lo)
// return;
if(hi <= lo + CUTOFF - 1){ //小陣列使用插入排序
insertionSort(a, lo, hi);
return;
}
sort(aux,a,lo,mid);
sort(aux,a,mid+1,hi);
if(!less(a[mid+1],a[mid])){ //如果陣列已經有序則直接複製,不再merge
System.arraycopy(aux, lo, a, lo, hi-lo+1);
return;
}
merge(a,aux,lo,mid,hi);
}
private static void insertionSort(Comparable[] a, int lo, int hi) {
for (int i = lo; i <= hi; i++) {
for(int j = i; j > lo && less(a[j],a[j-1]); j--)
exch(a,j,j-1);
}
}
private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi){
int i = lo, j = mid+1;
/*for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(less(aux[j],aux[i]))
a[k] = aux[j++];
else
a[k] = aux[i++];
} */
//消除陣列複製
for(int k = lo; k<= hi; k++){
if (i > mid) aux[k] = a[j++];
else if(j > hi) aux[k] = a[i++];
else if(less(a[j],a[i])) aux[k] = a[j++];
else aux[k] = a[i++];
}
}
快速歸併:
這裡修改merge()方法,目的是省掉內迴圈中總是判斷某半邊是否耗盡,做法是按索引值降序將a[ ]的後半部分複製到aux[ ]中,再去歸併。
private static void merge(Comparable[] a,Comparable[] aux, int lo, int mid, int hi){
for(int k = lo; k <= mid; k++)
aux[k] = a[k];
for(int k = mid+1; k <= hi; k++)
aux[k] = a[hi+mid+1-k];
int i = lo, j = hi; //由兩邊向中間比較
for(int k=lo;k<=hi; k++ ){
if(less(aux[j],aux[i]))
a[k] = aux[j--];
else
a[k] = aux[i++]; //保證穩定性
}
}
相關文章
- 四、歸併排序 && 快速排序排序
- go 實現歸併排序Go排序
- php實現 歸併排序,快速排序PHP排序
- java歸併排序Java排序
- 排序演算法__歸併排序排序演算法
- 排序演算法:歸併排序排序演算法
- 排序演算法 - 歸併排序排序演算法
- 排序演算法(歸併排序)排序演算法
- 歸併排序--排序演算法排序演算法
- 利用java實現插入排序、歸併排序、快排和堆排序Java排序
- 使用 Swift 實現歸併排序Swift排序
- 排序演算法之 '歸併排序'排序演算法
- 歸併排序MergeSort的C實現排序
- 全面瞭解歸併排序演算法及程式碼實現排序演算法
- 演算法之歸併排序演算法排序
- 三種語言實現歸併排序(C++/Python/Java)排序C++PythonJava
- 【演算法】排序02——歸併排序介紹及其在分治演算法思想上與快排的區別(含歸併程式碼)演算法排序
- 歸併排序的非遞迴實現排序遞迴
- 演算法:排序連結串列:歸併排序演算法排序
- 排序演算法之「歸併排序(Merge Sort)」排序演算法
- 演算法學習 – 歸併排序演算法排序
- 演算法學習 - 歸併排序演算法排序
- 8 大內部排序演算法相關及其java實現排序演算法Java
- 演算法(氣泡排序,快排,歸併排序)演算法排序
- 歸併排序與快速排序的一個實現與理解排序
- 利用遞迴實現連結串列的排序(歸併排序)遞迴排序
- C++快速排序與歸併排序的實現(LeetCode 912)C++排序LeetCode
- Sort排序專題(7)歸併排序(MergeSort)(C++實現)排序C++
- ForkJoin和氣泡排序組合實現的歸併排序排序
- 七、排序,選擇、冒泡、希爾、歸併、快速排序實現排序
- 直播系統原始碼,實現快速排序和歸併排序原始碼排序
- 歸併排序演算法(merge_Sort)排序演算法
- 從演算法開始[歸併排序]演算法排序
- 歸併排序排序
- 演算法與資料結構系列 ( 七 ) - 歸併排序- Merge Sort演算法資料結構排序
- 演算法之常見排序演算法-氣泡排序、歸併排序、快速排序演算法排序
- 快速排序&&歸併排序排序
- [資料結構與演算法]-排序演算法之插入排序(insertion sort)及其實現(Java)資料結構演算法排序Java
- 資料結構與演算法——排序演算法-歸併排序資料結構演算法排序