標籤:經典的 最好 nlog 技術 private ima col val 問題
基本思想
歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該演算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然後遞迴求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。
分而治之
可以看到這種結構很像一棵完全二叉樹,本文的歸併排序我們採用遞迴去實現(也可採用迭代的方式去實現)。分階段可以理解為就是遞迴拆分子序列的過程,遞迴深度為log2n。
合并相鄰有序子序列
再來看看治階段,我們需要將兩個已經有序的子序列合并成一個有序序列,比如中的最後一次合并,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合并為最終序列[1,2,3,4,5,6,7,8],來看下實現步驟。
代碼實現
import java.util.Arrays;/** * Created by chengxiao on 2016/12/8. */public class MergeSort { public static void main(String []args){ int []arr = {9,8,7,6,5,4,3,2,1}; sort(arr); System.out.println(Arrays.toString(arr)); } public static void sort(int []arr){ int []temp = new int[arr.length];//在排序前,先建好一個長度等於原數組長度的臨時數組,避免遞迴中頻繁開闢空間 sort(arr,0,arr.length-1,temp); } private static void sort(int[] arr,int left,int right,int []temp){ if(left<right){ int mid = (left+right)/2; sort(arr,left,mid,temp);//左邊歸併排序,使得左子序列有序 sort(arr,mid+1,right,temp);//右邊歸併排序,使得右子序列有序 merge(arr,left,mid,right,temp);//將兩個有序子數組合併作業 } } private static void merge(int[] arr,int left,int mid,int right,int[] temp){ int i = left;//左序列指標 int j = mid+1;//右序列指標 int t = 0;//臨時數組指標 while (i<=mid && j<=right){ if(arr[i]<=arr[j]){ temp[t++] = arr[i++]; }else { temp[t++] = arr[j++]; } } while(i<=mid){//將左邊剩餘元素填充進temp中 temp[t++] = arr[i++]; } while(j<=right){//將右序列剩餘元素填充進temp中 temp[t++] = arr[j++]; } t = 0; //將temp中的元素全部拷貝到原數組中 while(left <= right){ arr[left++] = temp[t++]; } }}
單向鏈表的歸併排序:
ListNode *sortList(ListNode *head) { if(head==nullptr||head->next==nullptr) return head; //採用快慢指標找到中間節點 ListNode *fast=head,*slow=head; while(fast!=nullptr&&fast->next!=nullptr&&fast->next->next!=nullptr){ fast=fast->next->next; slow=slow->next; } //斷開 fast=slow; slow=slow->next; fast->next=nullptr; fast=sortList(head); slow=sortList(slow); return merge(fast,slow); } ListNode* merge(ListNode* sub1,ListNode* sub2){ if(sub1==nullptr)return sub2; if(sub2==nullptr)return sub1; ListNode* head=nullptr; if(sub1->val<sub2->val){ head=sub1; sub1=sub1->next; } else{ head=sub2; sub2=sub2->next; } ListNode* p=head; while(sub1!=nullptr&&sub2!=nullptr){ if(sub1->val<sub2->val){ p->next=sub1; sub1=sub1->next; } else{ p->next=sub2; sub2=sub2->next; } p=p->next; } if(sub1!=nullptr) p->next=sub1; if(sub2!=nullptr) p->next=sub2; return head; }最後
歸併排序是穩定排序,它也是一種十分高效的排序,能利用完全二叉樹特性的排序一般效能都不會太差。java中Arrays.sort()採用了一種名為TimSort的排序演算法,就是歸併排序的最佳化版本。從上文的圖中可看出,每次合併作業的平均時間複雜度為O(n),而完全二叉樹的深度為|log2n|。總的平均時間複雜度為O(nlogn)。而且,歸併排序的最好,最壞,平均時間複雜度均為O(nlogn)。
圖解排序演算法(四)之歸併排序