常用演算法和原理
常見的排序演算法進包括:插入排序、選擇排序、氣泡排序、快速排序、堆排序、歸併排序、希爾排序、二叉樹排序、計數排序、桶排序、基數排序。
一、總述
演算法 | 複雜度 | 是否穩定 | |
插入排序(insertion sort) | 穩定 | ||
氣泡排序(bubble sort) | 穩定 | ||
歸併排序 (merge sort) | 穩定 | ||
二叉樹排序(Binary tree sort) | 穩定 | ||
計數排序 (counting sort) | 穩定 | ||
桶排序 (bucket sort) | 穩定 | ||
選擇排序(selection sort) | 不穩定 | ||
快速排序(quicksort)— O(nlogn) | 不穩定 | ||
堆排序 (heapsort) | 不穩定 | ||
希爾排序 (shell sort) | 不穩定 | ||
基數排序(radix sort) | 不穩定 |
二、插入排序
遍歷陣列,遍歷到i時,a0,a1...ai-1是已經排好序的,取出ai,從ai-1開始向前和每個比較大小,如果小於,則將此位置元素向後移動,繼續先前比較,如果不小於,則放到正在比較的元素之後。可見相等元素比較是,原來靠後的還是拍在後邊,所以插入排序是穩定的。
當待排序的資料基本有序時,插入排序的效率比較高,只需要進行很少的資料移動。
void insertion_sort (int a[], int n) { int i,j,v; for (i=1; i<n; i++) { //如果第i個元素小於第j個,則第j個向後移動 for (v=a[i], j=i-1; j>=0&&v<a[j]; j--) a[j+1]=a[j]; a[j+1]=v; } }
三、氣泡排序
氣泡排序的名字很形象,實際實現是相鄰兩節點進行比較,大的向後移一個,經過第一輪兩兩比較和移動,最大的元素移動到了最後,第二輪次大的位於倒數第二個,依次進行。這是最基本的氣泡排序,還可以進行一些優化。
優化一:如果某一輪兩兩比較中沒有任何元素交換,這說明已經都排好序了,演算法結束,可以使用一個Flag做標記,預設為false,如果發生互動則置為true,每輪結束時檢測Flag,如果為true則繼續,如果為false則返回。
優化二:某一輪結束位置為j,但是這一輪的最後一次交換髮生在lastSwap的位置,則lastSwap到j之間是排好序的,下一輪的結束點就不必是j--了,而直接到lastSwap即可,程式碼如下:
void bubble_sort (int a[], int n) { int i, j, lastSwap, tmp; for (j=n-1; j>0; j=lastSwap) { lastSwap=0; //每一輪要初始化為0,防止某一輪未發生交換,lastSwap保留上一輪的值進入死迴圈 for (i=0; i<j; i++) { if (a[i] > a[i+1]) { tmp=a[i]; a[i]=a[i+1]; a[i+1]=tmp; //最後一次交換位置的座標 lastSwap = i; } } } }
四、快速排序
是一種非常快的對比排序方法。它也Divide-And-Conquer思想的實現之一。自從其產生以來,快速排序理論得到了極大的改進,然而在實際中卻十分難以程式設計出正確健壯的程式碼。本文將對快速排序演算法的基本理論和程式設計實踐方面做作一個全面的講解。在本文講解中,將忽略很多細枝末節,試圖給讀者形成一個非常具體的快速排序形象。
1.快速排序---基本理論
因為該演算法是Divide-And-Conquer思想的一個實現,所以本文將以Divide-And-Conquer思想對其進行分析。首先,假設所要排序的數字儲存在陣列S中,則該演算法的操作可以拆分為兩部分:
- 在S中選出一個元素v;
- 將S陣列分為三個子陣列。其中v這個元素單獨形成子陣列1,比v小的元素形成子陣列2,比v大的元素形成自陣列3.
- 分別對子陣列2和子陣列3進行前兩步操作,實現遞迴排序;
- 返回時,依次返回S1,V,S2;
該程式具有平均執行時間T(n) = O(nlgn), 最差執行時間T(n) = O(n^2);
下面給出一個簡單的排序例項對以上演算法進行簡單說明:
初始陣列為--------------> S: 6,10,13,5,8,3,2,11
將第一個元素賦值給v----->v = 6;
以v為標準將S進行拆分--->[2,5,3],[6],[8,13,10,11] <----------將得到的陣列命名為S1, S2;
同樣對子陣列S1進行拆分->[ ], [2], [ 5, 3] <--------------------拆分之後,第一個子陣列為空。將得到的陣列命名為S12;
對子陣列S2進行拆分----->[ ], [8], [13, 10, 11]<---------------將得到的陣列命名為S22;
此時的陣列S為---------->2,5,3,6,8,13,10,11
對子陣列S12進行拆分---->[3], [5],[ ];
對自陣列S22進行拆分---->[10,11],[13],[]<--------------------將得到的陣列命名為S221
此時的陣列S為----------->2,3,5,6,8,10,11,13
對子陣列S221進行拆分--->[ ], [11], [13]
對後得到的陣列為-------->2,3,5,6,8,10,11,13;
int mpartition(int a[], int l, int r) { int pivot = a[l]; while (l<r) { while (l<r && pivot<=a[r]) r--; if (l<r) a[l++]=a[r]; while (l<r && pivot>a[l]) l++; if (l<r) a[r--]=a[l]; } a[l]=pivot; return l; } void quick_sort (int a[], int l, int r) { if (l < r) { int q = mpartition(a, l, r); msort(a, l, q-1); msort(a, q+1, r); } }
五、堆排序
堆排序是利用堆這種資料結構而設計的一種排序演算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均為O(nlogn),它也是不穩定排序。首先簡單瞭解下堆結構。
堆
堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。如下圖:
同時,我們對堆中的結點按層進行編號,將這種邏輯結構對映到陣列中就是下面這個樣子
該陣列從邏輯上講就是一個堆結構,我們用簡單的公式來描述一下堆的定義就是:
大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
ok,瞭解了這些定義。接下來,我們來看看堆排序的基本思想及基本步驟:
堆排序基本思想及步驟
堆排序的基本思想是:將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就為最大值。然後將剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。如此反覆執行,便能得到一個有序序列了
步驟一 構造初始堆。將給定無序序列構造成一個大頂堆(一般升序採用大頂堆,降序採用小頂堆)。
a.假設給定無序序列結構如下
2.此時我們從最後一個非葉子結點開始(葉結點自然不用調整,第一個非葉子結點 arr.length/2-1=5/2-1=1,也就是下面的6結點),從左至右,從下至上進行調整。
4.找到第二個非葉節點4,由於[4,9,8]中9元素最大,4和9交換。
這時,交換導致了子根[4,5,6]結構混亂,繼續調整,[4,5,6]中6最大,交換4和6。
此時,我們就將一個無需序列構造成了一個大頂堆。
步驟二 將堆頂元素與末尾元素進行交換,使末尾元素最大。然後繼續調整堆,再將堆頂元素與末尾元素交換,得到第二大元素。如此反覆進行交換、重建、交換。
a.將堆頂元素9和末尾元素4進行交換
b.重新調整結構,使其繼續滿足堆定義
c.再將堆頂元素8與末尾元素5進行交換,得到第二大元素8.
後續過程,繼續進行調整,交換,如此反覆進行,最終使得整個序列有序
再簡單總結下堆排序的基本思路:
a.將無需序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆;
b.將堆頂元素與末尾元素交換,將最大元素"沉"到陣列末端;
c.重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。
/** * Created by chengxiao on 2016/12/17. * 堆排序demo */ public class HeapSort { 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){ //1.構建大頂堆 for(int i=arr.length/2-1;i>=0;i--){ //從第一個非葉子結點從下至上,從右至左調整結構 adjustHeap(arr,i,arr.length); } //2.調整堆結構+交換堆頂元素與末尾元素 for(int j=arr.length-1;j>0;j--){ swap(arr,0,j);//將堆頂元素與末尾元素進行交換 adjustHeap(arr,0,j);//重新對堆進行調整 } } /** * 調整大頂堆(僅是調整過程,建立在大頂堆已構建的基礎上) * @param arr * @param i * @param length */ public static void adjustHeap(int []arr,int i,int length){ int temp = arr[i];//先取出當前元素i for(int k=i*2+1;k<length;k=k*2+1){//從i結點的左子結點開始,也就是2i+1處開始 if(k+1<length && arr[k]<arr[k+1]){//如果左子結點小於右子結點,k指向右子結點 k++; } if(arr[k] >temp){//如果子節點大於父節點,將子節點值賦給父節點(不用進行交換) arr[i] = arr[k]; i = k; }else{ break; } } arr[i] = temp;//將temp值放到最終的位置 } /** * 交換元素 * @param arr * @param a * @param b */ public static void swap(int []arr,int a ,int b){ int temp=arr[a]; arr[a] = arr[b]; arr[b] = temp; } }
結束
感謝: