穩定性:穩定排序算法會讓本來有相等鍵值的紀錄維持相對次序。也就是若是一個排序算法是穩定的,當有兩個相等鍵值的紀錄R和S,且在本來的列表中R出如今S以前,在排序過的列表中R也將會是在S以前。數組
冒泡排序經過重複地走訪過要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來,直到沒有再須要交換的元素爲止(對n個項目須要O(n^2)的比較次數)。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。數據結構
-
比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。 架構
-
對每一對相鄰元素作一樣的工做,從開始第一對到結尾的最後一對。這步作完後,最後的元素會是最大的數。ide
-
針對全部的元素重複以上的步驟,除了最後一個。函數
-
持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。 oop
冒泡排序爲一列數字進行排序的過程
O(n^2)
O(n)
O(n^2)
總共O(n),須要輔助空間O(1)
public static void main(String[] args) { int[] number = {95,45,15,78,84,51,24,12}; bubble_sort(number); for(int i = 0; i < number.length; i++) { System.out.print(number[i] + " "); } } public static void bubble_sort(int[] arr) { int temp, len = arr.length; for (int i = 0; i < len - 1; i++) for (int j = 0; j < len - 1 - i; j++) if (arr[j] > arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } }
經常使用的選擇排序方法有簡單選擇排序和堆排序,這裏只說簡單選擇排序,堆排序後面再說。
設所排序序列的記錄個數爲n,i 取 1,2,…,n-1 。
從全部n-i+1個記錄(Ri,Ri+1,…,Rn)中找出排序碼最小(或最大)的記錄,與第i個記錄交換。執行n-1趟 後就完成了記錄序列的排序。
以排序數組{3,2,1,4,6,5}爲例
在簡單選擇排序過程當中,所需移動記錄的次數比較少。最好狀況下,即待排序記錄初始狀態就已是正序排列了,則不須要移動記錄。
最壞狀況下,即待排序記錄初始狀態是按第一條記錄最大,以後的記錄從小到大順序排列,則須要移動記錄的次數最多爲3(n-1)。
簡單選擇排序過程當中須要進行的比較次數與初始狀態下待排序的記錄序列的排列狀況無關。
當i=1時,需進行n-1次比較;當i=2時,需進行n-2次比較;依次類推,共須要進行的比較次數是(n-1)+(n-2)+…+2+1=n(n-1)/2,即進行比較操做的時間複雜度爲O(n^2),進行移動操做的時間複雜度爲O(n)。
簡單選擇排序是不穩定排序。
public static void main(String[] args) { int[] number = {3,1,2,8,4,5,24,12}; SimpleSort(number); for(int i = 0; i < number.length; i++) { System.out.print(number[i] + " "); } } public static void SimpleSort(int[] arr) { int length=arr.length; int temp; for(int i=0;i<length-1;i++){ int min=i; for(int j=i+1;j<length;j++){ if(arr[j]<arr[min]){ min =j; } } if(min!=i){ temp = arr[min]; arr[min]=arr[i]; arr[i]=temp; } } }
希爾排序法(縮小增量法) 屬於插入類排序,是將整個無序列分割成若干小的子序列分別進行插入排序的方法。
把記錄按下標的必定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減小,每組包含的關鍵詞愈來愈多,當增量減至1時,整個文件恰被分紅一組,算法便終止。
希爾排序是基於插入排序的如下兩點性質而提出改進方法的:
先取一個正整數d1小於n,把全部序號相隔d1的數組元素放一組,組內進行直接插入排序;而後取d2小於d1,重複上述分組和排序操做;直至di=1,即全部記錄放進一個組中排序爲止。
例如,假設有這樣一組數[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],若是咱們以步長爲5開始進行排序,咱們能夠經過將這列表放在有5列的表中來更好地描述算法,這樣他們就應該看起來是這樣:
13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
而後咱們對每列進行排序:
10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
將上述四行數字,依序接在一塊兒時咱們獲得:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].這時10已經移至正確位置了,而後再以3爲步長進行排序:
10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序以後變爲:
10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最後以1步長進行排序(此時就是簡單的插入排序了)。
希爾排序是一個不穩定的排序,其時間複雜度受步長(增量)的影響。
空間複雜度: O(1)
時間複雜度: 平均 O(n^1.3)
最好 O(n)
最壞 O(n^2)
public static void shellSort(int[] a) { int gap = 1, i, j, len = a.length; int temp; while (gap < len / 3){ gap = gap * 3 + 1; } for (; gap > 0; gap /= 3){ for (i = gap; i < len; i++) { temp = a[i]; for (j = i - gap; j >= 0 && a[j] > temp; j -= gap){ a[j + gap] = a[j]; } a[j + gap] = temp; } } }
歸併排序,是建立在歸併操做上的一種有效的排序算法該算法是採用分治法(Divide and Conquer)的一個很是典型的應用,且各層分治遞歸能夠同時進行。
即先使每一個子序列有序,再將兩個已經排序的序列合併成一個序列的操做。若將兩個有序表合併成一個有序表,稱爲二路歸併。
例如:
設有數列{6,202,100,301,38,8,1}
初始狀態:6,202,100,301,38,8,1
第一次歸併後:{6,202},{100,301},{8,38},{1},比較次數:3;
第二次歸併後:{6,100,202,301},{1,8,38},比較次數:4;
第三次歸併後:{1,6,8,38,100,202,301},比較次數:4;
總的比較次數爲:3+4+4=11,;
逆序數爲14;
歸併排序示意圖
歸併排序速度僅次於快速排序,爲穩定排序算法(即相等的元素的順序不會改變),通常用於對整體無序,可是各子項相對有序的數列.
時間複雜度爲O(nlogn)
空間複雜度爲 O(n)
歸併排序比較佔用內存,但倒是一種效率高且穩定的算法。
①申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
②設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
③比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
④重複步驟③直到某一指針到達序列尾
⑤將另外一序列剩下的全部元素直接複製到合併序列尾
public static void main(String[] args) { int [] arr ={6,5,3,1,8,7,2,4}; merge_sort(arr); for(int i : arr){ System.out.println(i); } } public static void merge_sort(int[] arr) { int len = arr.length; int[] result = new int[len]; int block, start; for(block = 1; block <=len ; block *= 2) { for(start = 0; start <len; start += 2 * block) { int low = start; int mid = (start + block) < len ? (start + block) : len; int high = (start + 2 * block) < len ? (start + 2 * block) : len; int start1 = low, end1 = mid; int start2 = mid, end2 = high; while (start1 < end1 && start2 < end2) { result[low++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++]; } while(start1 < end1) { result[low++] = arr[start1++]; } while(start2 < end2) { result[low++] = arr[start2++]; } } int[] temp = arr; arr = result; result = temp; } }
假設序列共有n個元素
①將序列每相鄰兩個數字進行歸併操做,造成floor(n/2)個序列,排序後每一個序列包含兩個元素。
②將上述序列再次歸併,造成floor(n/4)個序列,每一個序列包含四個元素
③重複步驟②,直到全部元素排序完畢
public static void main(String[] args) { int [] arr ={6,5,3,1,8,7,2,4}; int len = arr.length; int[] reg = new int[len]; merge_sort_recursive(arr,reg,0,len-1); for(int i : arr){ System.out.println(i); } } static void merge_sort_recursive(int[] arr, int[] reg, int start, int end) { if (start >= end) return; int len = end - start, mid = (len >> 1) + start; int start1 = start, end1 = mid; int start2 = mid + 1, end2 = end; merge_sort_recursive(arr, reg, start1, end1); merge_sort_recursive(arr, reg, start2, end2); int k = start; while (start1 <= end1 && start2 <= end2) reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++]; while (start1 <= end1) reg[k++] = arr[start1++]; while (start2 <= end2) reg[k++] = arr[start2++]; for (k = start; k <= end; k++) arr[k] = reg[k]; }
快速排序(Quicksort)是對冒泡排序的一種改進,又稱劃分交換排序(partition-exchange sort。
快速排序使用分治法(Divide and conquer)策略來把一個序列(list)分爲兩個子序列(sub-lists)。
步驟爲:
①.從數列中挑出一個元素,稱爲」基準」(pivot)
②.從新排序數列,全部元素比基準值小的擺放在基準前面,全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。在這個分區結束以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做。
③.遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序
使用快速排序法對一列數字進行排序的過程
在平均情況下,排序n個項目要Ο(n log n)次比較。在最壞情況下則須要Ο(n2)次比較,但這種情況並不常見。事實上,快速排序一般明顯比其餘Ο(n log n)算法更快,由於它的內部循環(inner loop)能夠在大部分的架構上頗有效率地被實現出來。
最差時間複雜度 Ο(n^2)
最優時間複雜度 Ο(n log n)
平均時間複雜度Ο(n log n)
最差空間複雜度 根據實現的方式不一樣而不一樣
public static void main(String[] args) { int [] arr = {8,1,0,4,6,2,7,9,5,3}; quickSort(arr,0,arr.length-1); for(int i :arr){ System.out.println(i); } } public static void quickSort(int[]arr,int low,int high){ if (low < high) { int middle = getMiddle(arr, low, high); quickSort(arr, low, middle - 1); quickSort(arr, middle + 1, high); } } public static int getMiddle(int[] list, int low, int high) { int tmp = list[low]; while (low < high) { while (low < high && list[high] >= tmp) { high--; } list[low] = list[high]; while (low < high && list[low] <= tmp) { low++; } list[high] = list[low]; } list[low] = tmp; return low; }
運行結果:
分析:
取8爲中值,紅色箭頭表示low,綠色箭頭表示high
①從high開始向前掃描到第一個比8小的值與8交換。
②從low向後掃描第一比8大的值與8交換。
③重複①②過程只到,high=low完成一次快速排序,而後遞歸子序列。
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法,它是選擇排序的一種。能夠利用數組的特色快速定位指定索引的元素。堆分爲大根堆和小根堆,是徹底二叉樹。大根堆的要求是每一個節點的值都不大於其父節點的值。
因爲堆中每次都只能刪除第0個數據,經過 取出第0個數據再執行堆的刪除操做、重建堆(實際的操做是將最後一個數據的值賦給根結點,而後再從根結點開始進行一次從上向下的調整。),而後再取,如此重複實現排序。
堆的操做:
在堆的數據結構中,堆中的最大值老是位於根節點。堆中定義如下幾種操做:
堆的存儲:
一般堆是經過一維數組來實現的。在數組起始位置爲0的情形中:
public class HeapSort { private static int[] sort = new int[]{1,0,10,20,3,5,6,4,9,8,12,17,34,11}; public static void main(String[] args) { buildMaxHeapify(sort); heapSort(sort); print(sort); } private static void buildMaxHeapify(int[] data){ int startIndex = getParentIndex(data.length - 1); for (int i = startIndex; i >= 0; i--) { maxHeapify(data, data.length, i); } } /** * 建立最大堆 * @param data * @param heapSize須要建立最大堆的大小,通常在sort的時候用到,由於最多值放在末尾,末尾就再也不納入最大堆了 * @param index當前須要建立最大堆的位置 */ private static void maxHeapify(int[] data, int heapSize, int index){ int left = getChildLeftIndex(index); int right = getChildRightIndex(index); int largest = index; if (left < heapSize && data[index] < data[left]) { largest = left; } if (right < heapSize && data[largest] < data[right]) { largest = right; } if (largest != index) { int temp = data[index]; data[index] = data[largest]; data[largest] = temp; maxHeapify(data, heapSize, largest); } } /** * 排序,最大值放在末尾,data雖然是最大堆,在排序後就成了遞增的 * @param data */ private static void heapSort(int[] data) { for (int i = data.length - 1; i > 0; i--) { int temp = data[0]; data[0] = data[i]; data[i] = temp; maxHeapify(data, i, 0); } } /** * 父節點位置 * @param current * @return */ private static int getParentIndex(int current){ return (current - 1) >> 1; } /** * 左子節點position注意括號,加法優先級更高 * @param current * @return */ private static int getChildLeftIndex(int current){ return (current << 1) + 1; } /** * 右子節點position * @param current * @return */ private static int getChildRightIndex(int current){ return (current << 1) + 2; } private static void print(int[] data){ for (int i = 0; i < data.length; i++) { System.out.print(data[i] + " |"); } } }
桶排序(Bucket sort)或所謂的箱排序,是一個排序算法。
假設有一組長度爲N的待排關鍵字序列K[1….n]。首先將這個序列劃分紅M個的子區間(桶) 。而後基於某種映射函數 ,將待排序列的關鍵字k映射到第i個桶中(即桶數組B的下標 i) ,那麼該關鍵字k就做爲B[i]中的元素。接着對每一個桶B[i]中的全部元素進行比較排序(可使用快排)。而後依次枚舉輸出B[0]….B[M]中的所有內容便是一個有序序列。
桶排序的步驟:
①設置一個定量的數組看成空桶子。
②尋訪序列,而且把項目一個一個放到對應的桶子去。
③對每一個不是空的桶子進行排序。
④從不是空的桶子裏把項目再放回原來的序列中。
數據結構 數組
最差時間複雜度 O(n^2)
平均時間複雜度 O(n+k)
最差空間複雜度 O(n*k)
平均狀況下桶排序以線性時間運行,桶排序是穩定的,排序很是快,可是同時也很是耗空間,基本上是最耗空間的一種排序算法。
對N個關鍵字進行桶排序的時間複雜度分爲兩個部分:
①循環計算每一個關鍵字的桶映射函數,這個時間複雜度是O(N)。
②利用先進的比較排序算法對每一個桶內的全部數據進行排序,其時間複雜度爲 ∑ O(Ni*logNi) 。其中Ni 爲第i個桶的數據量。
很顯然,第②部分是桶排序性能好壞的決定因素。儘可能減小桶內數據的數量是提升效率的惟一辦法(由於基於比較排序的最好平均時間複雜度只能達到O(N*logN)了)。所以,咱們須要儘可能作到下面兩點:
① 映射函數f(k)可以將N個數據平均的分配到M個桶中,這樣每一個桶就有[N/M]個數據量。
②儘可能的增大桶的數量。極限狀況下每一個桶只能獲得一個數據,這樣就徹底避開了桶內數據的「比較」排序操做。 固然,作到這一點很不容易,數據量巨大的狀況下,f(k)函數會使得桶集合的數量巨大,空間浪費嚴重。這就是一個時間代價和空間代價的權衡問題了。
對0~1之間的一組浮點數進行升序排序:
BucketSort.Java
public class BucketSort { /** * 對arr進行桶排序,排序結果仍放在arr中 */ public static void bucketSort(double arr[]){ int n = arr.length; ArrayList bucketList[] = new ArrayList [n]; for(int i =0;i<n;i++){ int temp = (int) Math.floor(n*arr[i]); if(null==bucketList[temp]) bucketList[temp] = new ArrayList(); bucketList[temp].add(arr[i]); } for(int i = 0;i<n;i++){ if(null!=bucketList[i]) insert(bucketList[i]); } int count = 0; for(int i = 0;i<n;i++){ if(null!=bucketList[i]){ Iterator iter = bucketList[i].iterator(); while(iter.hasNext()){ Double d = (Double)iter.next(); arr[count] = d; count++; } } } } /** * 用插入排序對每一個桶進行排序 * 從小到大排序 */ public static void insert(ArrayList list){ if(list.size()>1){ for(int i =1;i<list.size();i++){ if((Double)list.get(i)<(Double)list.get(i-1)){ double temp = (Double) list.get(i); int j = i-1; for(;j>=0&&((Double)list.get(j)>(Double)list.get(j+1));j--) list.set(j+1, list.get(j)); list.set(j+1, temp); } } } } }
測試代碼:
public static void main(String[] args) { double arr [] ={0.21,0.23,0.76,0.12,0.89}; BucketSort.bucketSort(arr); for(double a:arr){ System.out.println(a); } }
輸出結果:
基數排序(Radix sort)是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序也不是隻能使用於整數。
將全部待比較數值(正整數)統一爲一樣的數位長度,數位較短的數前面補零。而後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成之後,數列就變成一個有序序列。
基數排序的時間複雜度是O(k·n),其中n是排序元素個數,k是數字位數。注意這不是說這個時間複雜度必定優於O(n·log(n)),k的大小取決於數字位的選擇和待排序數據所屬數據類型的全集的大小;k決定了進行多少輪處理,而n是每輪處理的操做數目。
基數排序基本操做的代價較小,k通常不大於logn,因此基數排序通常要快過基於比較的排序,好比快速排序。
最差空間複雜度是O(k·n)
如今有數組:278,109,63,930,589,184,505,269,8,83 。根據各位數將數組劃分爲10個鏈表(固然其中的某些鏈表可能不含有元素)
第一次分配:
0:930
1:
2:
3:63,83
4:184
5:505
6:
7:
8:278,8
9:109,589,269
第一次收集後的數組:
930,63,83,184,505,278,8,109,589,269
第二次分配:
0:505,8,109
1:
2:
3:930
4:
5:
6:63,269
7:278
8:83,184,589
9:
第二次收集後的數組:
505,8,109,930,63,269,278,83,184,589
第三次分配:
0:8,63,83
1:109,184
2:278,269
3:
4:
5:505,589
6:
7:
8:
9:930
最後獲得序列:
8,63,83,109,184,269,278,505,589,930
基數排序實際上是利用多關鍵字先達到局部有序,再調整達到全局有序。
代碼實現:
public class Test { public static void main(String[] args) { int[] array = {278,109,63,930,589,184,505,269,8,83}; radixSort(array); for(double a : array){ System.out.println(a); } } public static void radixSort(int[] array){ int max=array[0]; for(int i=1;i<array.length;i++){ if(array[i]>max){ max=array[i]; } } int time=0; while(max>0){ max/=10; time++; } List<List<Integer>> list=new ArrayList<List<Integer>>(); for(int i=0;i<10;i++){ List<Integer> item=new ArrayList<Integer>(); list.add(item); } for(int i=0;i<time;i++){ for(int j=0;j<array.length;j++){ int index = array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i); list.get(index).add(array[j]); } int count=0; for(int k=0;k<10;k++){ if(list.get(k).size()>0){ for(int a : list.get(k)){ array[count]=a; count++; } list.get(k).clear(); } } } } }
運行結果:
將一個數據插入到已經排好序的有序數據中,從而獲得一個新的、個數加一的有序數據,算法適用於少許數據的排序,是穩定的排序方法。
插入排序又分爲 直接插入排序 和 折半插入排序。
把待排序的紀錄按其關鍵碼值的大小逐個插入到一個已經排好序的有序序列中,直到全部的紀錄插入完爲止,獲得一個新的有序序列。
public static void insertSort(int a[]){ int j; int preJ; int key; for(j=1;j<a.length;j++){ key=a[j]; preJ=j-1; while(preJ>=0 && a[preJ]>key){ a[preJ+1]=a[preJ]; preJ--; } a[preJ+1]=key; } }
備註很清楚,我就很少說了....
空間複雜度O(1)
平均時間複雜度O(n^2)
最差狀況:反序,須要移動n*(n-1)/2個元素 ,運行時間爲O(n^2)。
最好狀況:正序,不須要移動元素,運行時間爲O(n).
直接插入排序中要把插入元素與已有序序列元素依次進行比較,效率很是低。
折半插入排序,使用使用折半查找的方式尋找插入點的位置, 能夠減小比較的次數,但移動的次數不變, 時間複雜度和空間複雜度和直接插入排序同樣,在元素較多的狀況下能提升查找性能。
private static void binaryInsertSort(int[] a) { for(int i = 1; i < a.length; i++) { int key = a[i]; int pre = 0; int last = i - 1; while(pre <= last) { int mid = (pre + last) / 2; if(key < a[mid]) { last = mid - 1; } else { pre = mid + 1; } } for(int j = i; j >= pre + 1; j a[j] = a[j - 1]; } a[pre] = key; } }
直接插入排序是,比較一個後移一個;
折半插入排序是,先找到位置,而後一塊兒移動;
做用:給定一個數組arr[]和數組中任意一個元素a,重排數組使得a左邊都小於它,右邊都不小於它。
static int partition(int A[], int start, int end, int pivotIndex){ int i = start, j = end, pivot = A[pivotIndex]; swap<int>(A[end], A[pivotIndex]); while(i < j){ while(i < j && A[i] <= pivot) ++i; while(i < j && A[j] >= pivot) --j; if(i < j) swap<int>(A[i], A[j]); } swap<int>(A[end], A[i]); return i; }
思路:
①、加一個標誌位,當某一趟冒泡排序沒有元素交換時,則冒泡結束,元素已經有序,能夠有效的減小冒泡次數。
/** * 引入標誌位,默認爲true * 若是先後數據進行了交換,則爲true,不然爲false。若是沒有數據交換,則排序完成。 */ public static int[] bubbleSort(int[] arr){ boolean flag = true; int n = arr.length; while(flag){ flag = false; for(int j=0;j<n-1;j++){ if(arr[j] >arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; flag = true; } } n--; } return arr; }
②、記錄每一次元素交換的位置,當元素交換的位置在第0個元素時,則排序結束。
① 快速排序在處理小規模數據時的表現很差,這個時候能夠改用插入排序。
②對於一個每一個元素都徹底相同的一個序列來說,快速排序也會退化到 O(n^2)。要將這種狀況避免到,能夠這樣作:
在分區的時候,將序列分爲 3 堆,一堆小於中軸元素,一堆等於中軸元素,一堆大於中軸元素,下次遞歸調用快速排序的時候,只需對小於和大於中軸元素的兩堆數據進行排序,中間等於中軸元素的一堆已經放好。
轉自 http://blog.csdn.net/amazing7/article/details/51603682