當咱們進行數據處理的時候,每每須要對數據進行查找操做,一個有序的數據集每每可以在高效的查找算法下快速獲得結果。因此排序的效率就會顯的十分重要,本篇咱們將着重的介紹幾個常見的排序算法,涉及以下內容:java
1、排序相關的基本概念
排序實際上是一個至關大的概念,主要分爲兩類:內部排序和外部排序。而咱們一般所說的各類排序算法其實指的是內部排序算法。內部排序是基於內存的,整個排序過程都是在內存中完成的,而外部排序指的是因爲數據量太大,內存不能徹底容納,排序的時候須要藉助外存才能完成(經常是算計着某一部分已經計算過的數據移出內存讓另外一部分未被計算的數據進入內存)。而咱們本篇文章將主要介紹內排序中的幾種經常使用排序算法:算法
還有一個概念問題,排序的穩定性問題。若是Ai = Aj,排序前Ai在Aj以前,排序後Ai還在Aj以前,則稱這種排序算法是穩定的,不然說明該算法不穩定。數組
2、插入類排序算法
插入類排序算法的核心思想是,在一個有序的集合中,咱們將當前值插入到適合位置上,使得插入結束以後整個集合依然是有序的。那咱們接下來就學習下這幾種同一類別的不一樣實現。函數
一、直接插入排序
直接插入排序算法的核心思想是,將第 i 個記錄插入到前面 i-1 個已經有序的集合中。下圖是一個完整的直接插入排序過程:學習
由於一個元素確定是有序的,i 等於 2 的時候,將第二個元素插入到前 i-1個有序集合中,當 i 等於3的時候,將第三個元素插入到前 i-1(2)集合中,等等。直到咱們去插入最後一個元素的時候,前面的 i-1 個元素構成的集合已是有序的了,因而咱們找到第 i 個元素的合適位置插入便可,整個插入排序完成。下面是具體的實現代碼:ui
public class test3 {
public static void InsertSort(int[] array){
int i=0,j=0,key;
for (i=1;i<10;i++){
key = array[i];
j = i-1;
while(j>=0&&key<array[j]){
//須要移動位置,將較大的值array[j]向後移動一個位置
array[j+1] = array[j];
j--;
}
//循環結束說明找到適當的位置了,是時候插入值了
array[j+1] = key;
}
//輸出排序後的數組內容
for (int value : array){
System.out.print(value+",");
}
}
public static void main(String[] args){
//主函數中對其進行調用
int[] array = {1,13,72,9,22,4,6,781,29,2};
InsertSort(array);
}
}
整個程序的邏輯是從數組的第二個元素開始,每一個元素都以其前面全部的元素爲基本,找到合適的位置進行插入。對於這種按照從小到大的排序原則,程序使用一個臨時變量temp保存當前須要插入的元素的值,從前面的子序列的最後一個元素開始,循環的與temp進行比較,一旦發現有大於temp的元素,讓它順序的日後移動一個位置,直到找到一個元素小於temp,那麼就找到合適的插入位置了。spa
由於咱們使用的判斷條件是,key>array[j]。因此來講,插入排序算法也是穩定的算法。對於值相同的元素並不會更改他們原來的位置順序。至於該算法的效率,最好的狀況是全部元素都已有序,比較次數爲n-1,最壞的狀況是全部元素都是逆序的,比較次數爲(n+2)(n-1)/2,因此該算法的時間複雜度爲O(n*n)。3d
二、二分折半插入排序
既然咱們每次要插入的序列是有序的,咱們徹底可使用二分查找到合適位置再進行插入,這顯然要比直接插入的效率要高一些。代碼比較相似,再也不作解釋。指針
public class test4 { public static void halfInsertSort(int[] array){ for(int k=1;k<array.length;k++){ int key = array[k]; //找到合適的位置 int low,high,mid; low = 0;high = k-1; while(low <= high){ mid = (low+high)/2; if(key == array[mid])break; else if(key > array[mid]){ low = mid+1; }else{ high = mid-1; } } //low的索引位置就是即將插入的位置 //移動low索引位置後面的全部元素 for(int x=k-1;x>=low;x--){ array[x+1] = array[x]; } array[low] = key; } //遍歷輸出有序隊列內容 for(int key:array){ System.out.print(key + ","); } } public static void main(String[] args){ int[] array = {1,13,72,9,22,4,6,781,29,2}; halfInsertSort(array); } }
雖然,折半插入改善了查找插入位置的比較次數,可是移動的時間耗費並無獲得改善,因此效率上優秀的量可觀,時間複雜度仍然爲O(n*n)。code
二、希爾排序
直接插入排序在整個待排序序列基本有序的狀況下,效率最佳,但咱們每每不能保證每次待排序的序列都是基本有序的。希爾排序就是基於這樣的情形,它將待排序序列拆分紅多個子序列,保證每一個子序列的組成元素相對較少,而後經過對子序列使用直接排序。對於本就容量不大的子序列來講,直接排序的效率是至關優秀的。
希爾排序算法使用一個距離增量來切分子序列,例如:
如圖,咱們初始有一個序列,按照距離增量爲4來拆分的話,能夠將整個序列拆分紅四個子序列,咱們對四個子序列內部進行直接插入排序獲得結果以下:
修改距離增量從新劃分子序列:
很顯然,當距離增量變小的時候,序列的個數也會變少,可是這些子序列的內部都基本有序,當對他們進行直接插入排序的時候會使得效率變高。一旦距離增量減小爲1,那麼子序列的個數也將減小爲1,也就是咱們的原序列,而此時的序列內部基本有序,最後執行一次直接插入排序完成整個排序操做。
下面咱們看算法是的具體實現:
public class test4 { /*漸減delete的值*/ public static void ShellSort(){ int[] array = {46,55,13,42,94,17,5,70}; int[] delets = {4,2,1}; for (int i=0;i<delets.length;i++){ oneShellSort(array,delets[i]); } //遍歷輸出數組內容 for(int value : array){ System.out.print(value + ","); } } /*根據距離增量的值劃分子序列並對子序列內部進行直接插入排序*/ public static void oneShellSort(int[] array,int delet){ int temp; for(int i=delet;i<array.length;i++){ //從第二個子序列開始交替進行直接的插入排序 //將當前元素插入到前面的有序隊列中 if(array[i-delet] > array[i]){ temp = array[i]; int j=i-delet; while(j>=0 && array[j] > temp){ array[j+delet] = array[j]; j -= delet; } array[j + delet] = temp; } } } public static void main(String[] args){ ShellSort(); } }
3、交換類排序
交換類的排序算法通常是利用兩個元素之間的值的大小進行比較運算,而後移動外置實現的,這類排序算法主要有兩種:
一、冒泡排序
冒泡排序經過兩兩比較,每次將最大或者最小的元素移動到整個序列的一端。這種排序至關常見,也比較簡單,直接上代碼:
public class test4 { public static void bubbleSort(int[] array){ int temp = 0; for(int i=0;i<array.length-1;i++){ for(int j =0;j<array.length-1-i;j++){ if(array[j]>array[j+1]){ //交換兩個數組元素的值 temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } //遍歷輸出數組元素 for(int value : array){ System.out.print(value + ","); } } public static void main(String[] args){ int[] array={2,1,55,66,44,22,99,101,100}; bubbleSort(array); } }
二、快速排序
有沒有既不浪費空間又能夠快一點的排序算法呢?那就是「快速排序」啦!光聽這個名字是否是就以爲很高端呢。
假設咱們如今對「6 1 2 7 9 3 4 5 10 8」這個10個數進行排序。首先在這個序列中隨便找一個數做爲基準數(不要被這個名詞嚇到了,就是一個用來參照的數,待會你就知道它用來作啥的了)。爲了方便,就讓第一個數6做爲基準數吧。接下來,須要將這個序列中全部比基準數大的數放在6的右邊,比基準數小的數放在6的左邊,相似下面這種排列:
3 1 2 5 4 6 9 7 10 8
在初始狀態下,數字6在序列的第1位。咱們的目標是將6挪到序列中間的某個位置,假設這個位置是k。如今就須要尋找這個k,而且以第k位爲分界點,左邊的數都小於等於6,右邊的數都大於等於6。想想,你有辦法能夠作到這點嗎?
方法其實很簡單:分別從初始序列「6 1 2 7 9 3 4 5 10 8」兩端開始「探測」。先從右往左找一個小於6的數,再從左往右找一個大於6的數,而後交換他們。這裏能夠用兩個變量i和j,分別指向序列最左邊和最右邊。咱們爲這兩個變量起個好聽的名字「哨兵i」和「哨兵j」。剛開始的時候讓哨兵i指向序列的最左邊(即i=1),指向數字6。讓哨兵j指向序列的最右邊(即=10),指向數字。
首先哨兵j開始出動。由於此處設置的基準數是最左邊的數,因此須要讓哨兵j先出動,這一點很是重要(請本身想想爲何)。哨兵j一步一步地向左挪動(即j–),直到找到一個小於6的數停下來。接下來哨兵i再一步一步向右挪動(即i++),直到找到一個數大於6的數停下來。最後哨兵j停在了數字5面前,哨兵i停在了數字7面前。
如今交換哨兵i和哨兵j所指向的元素的值。交換以後的序列以下:
6 1 2 5 9 3 4 7 10 8
到此,第一次交換結束。接下來開始哨兵j繼續向左挪動(再友情提醒,每次必須是哨兵j先出發)。他發現了4(比基準數6要小,知足要求)以後停了下來。哨兵i也繼續向右挪動的,他發現了9(比基準數6要大,知足要求)以後停了下來。此時再次進行交換,交換以後的序列以下:
6 1 2 5 4 3 9 7 10 8
第二次交換結束,「探測」繼續。哨兵j繼續向左挪動,他發現了3(比基準數6要小,知足要求)以後又停了下來。哨兵i繼續向右移動,糟啦!此時哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。說明此時「探測」結束。咱們將基準數6和3進行交換。交換以後的序列以下:
3 1 2 5 4 6 9 7 10 8
到此第一輪「探測」真正結束。此時以基準數6爲分界點,6左邊的數都小於等於6,6右邊的數都大於等於6。回顧一下剛纔的過程,其實哨兵j的使命就是要找小於基準數的數,而哨兵i的使命就是要找大於基準數的數,直到i和j碰頭爲止。
OK,解釋完畢。如今基準數6已經歸位,它正好處在序列的第6位。此時咱們已經將原來的序列,以6爲分界點拆分紅了兩個序列,左邊的序列是「3 1 2 5 4」,右邊的序列是「9 7 10 8」。接下來還須要分別處理這兩個序列。由於6左邊和右邊的序列目前都仍是很混亂的。不過沒關係,咱們已經掌握了方法,接下來只要模擬剛纔的方法分別處理6左邊和右邊的序列便可。如今先來處理6左邊的序列現吧。
左邊的序列是「3 1 2 5 4」。請將這個序列以3爲基準數進行調整,使得3左邊的數都小於等於3,3右邊的數都大於等於3。好了開始動筆吧
若是你模擬的沒有錯,調整完畢以後的序列的順序應該是:
2 1 3 5 4
OK,如今3已經歸位。接下來須要處理3左邊的序列「2 1」和右邊的序列「5 4」。對序列「2 1」以2爲基準數進行調整,處理完畢以後的序列爲「1 2」,到此2已經歸位。序列「1」只有一個數,也不須要進行任何處理。至此咱們對序列「2 1」已所有處理完畢,獲得序列是「1 2」。序列「5 4」的處理也仿照此方法,最後獲得的序列以下:
1 2 3 4 5 6 9 7 10 8
對於序列「9 7 10 8」也模擬剛纔的過程,直到不可拆分出新的子序列爲止。最終將會獲得這樣的序列,以下
1 2 3 4 5 6 7 8 9 10
到此,排序徹底結束。細心的同窗可能已經發現,快速排序的每一輪處理其實就是將這一輪的基準數歸位,直到全部的數都歸位爲止,排序就結束了。下面上個霸氣的圖來描述下整個算法的處理過程。
這是爲何呢?
快速排序之所比較快,由於相比冒泡排序,每次交換是跳躍式的。每次排序的時候設置一個基準點,將小於等於基準點的數所有放到基準點的左邊,將大於等於基準點的數所有放到基準點的右邊。這樣在每次交換的時候就不會像冒泡排序同樣每次只能在相鄰的數之間進行交換,交換的距離就大的多了。所以總的比較和交換次數就少了,速度天然就提升了。固然在最壞的狀況下,仍多是相鄰的兩個數進行了交換。所以快速排序的最差時間複雜度和冒泡排序是同樣的都是O(N2),它的平均時間複雜度爲O(NlogN)。其實快速排序是基於一種叫作「二分」的思想。咱們後面還會遇到「二分」思想,到時候再聊。先上代碼,以下
public class test4 { public static void quickSort(int[] arr,int low,int high){ int i,j,temp,t; if(low>high){ return; } i=low; j=high; //temp就是基準位 temp = arr[low]; while (i<j) { //先看右邊,依次往左遞減 while (temp<=arr[j]&&i<j) { j--; } //再看左邊,依次往右遞增 while (temp>=arr[i]&&i<j) { i++; } //若是知足條件則交換 if (i<j) { t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } //最後將基準爲與i和j相等位置的數字交換 arr[low] = arr[i]; arr[i] = temp; //遞歸調用左半數組 quickSort(arr, low, j-1); //遞歸調用右半數組 quickSort(arr, j+1, high); } public static void main(String[] args){ int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19}; quickSort(arr, 0, arr.length-1); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } }
4、選擇類排序
選擇類排序的基本思想是,每一趟會在n個元素中比較n-1次,選擇出最大或者最小的一個元素放在整個序列的端點處。選擇類排序有基於樹的也有基於線性表的,有關樹結構的各類排序算法,咱們將在後續文章中進行描述,此處咱們實現簡單的選擇排序算法。
public class test4 { public static void ChooseSort(int[] array){ for (int i=0;i<array.length;i++){ for (int j=i+1;j<array.length;j++){ if(array[i]>array[j]){ //發現比本身小的元素,則交換位置 int temp = array[j]; array[j]=array[i]; array[i] = temp; } } } //輸出排序後的數組內容 for (int key : array){ System.out.print(key+","); } } public static void main(String[] args){ int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19}; ChooseSort(arr); } }
5、歸併類排序算法
這裏的歸併類排序算法指的就是歸併排序。歸併排序的核心思想是,對於一個初始的序列不斷遞歸,直到子序列中的元素足夠少時,對他們進行直接排序。而後遞歸返回繼續對兩個分別有序的序列進行直接排序,最終遞歸結束的時候,整個序列必然是有序的。
對於一個初始序列,咱們遞歸拆分的結果如上圖。最小的子序列只有兩個元素,咱們能夠輕易的對他們進行直接的排序。簡單的排序結果以下:
而後咱們遞歸返回:
初看起來和咱們的希爾排序的基本思想有點像,希爾排序經過對初始序列的稀疏化,使得每一個子序列在內部上都是有序的,最終在對整個序列進行排序的時候,序列的內部基本有序,整體上能提升效率。可是咱們的歸併排序的和核心思想是,經過不斷的遞歸,直到子序列元素足夠少,在內部對他們進行直接的排序操做,當遞歸返回的時候,對返回的兩個子表再次進行歸併排序,使得合成的新序列是有序的,一直到遞歸返回調用結束時候,整個序列就是有序的。
import java.util.Arrays; public class test4 { //歸併排序 /*歸併排序採用遞歸實現 * 分階段能夠理解爲就是遞歸拆分子序列的過程、 * 治階段,咱們須要將兩個已經有序的子序列合併成一個有序序列,好比上圖中的最後一次合併,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合併爲最終序列[1,2,3,4,5,6,7,8], * */ 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++]; } } }