排序算法以及其java实现
一、術語了解
- 穩定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
- 不穩定:如果a原本在b的前面,而a=b,排序之后a可能會出現在b的后面;
- 內排序:所有排序操作都在內存中完成;
- 外排序:由于數據太大,因此把數據放在磁盤中,而排序通過磁盤和內存的數據傳輸才能進行;
- 時間復雜度:?一個算法執行所耗費的時間。
- 空間復雜度:運行完一個程序所需內存的大小。
圖片名詞解釋:
- n: 數據規模
- k: “桶”的個數
- In-place: 占用常數內存,不占用額外內存
- Out-place: 占用額外內存
二、基礎算法
1、冒泡算法
(1)思想:兩兩比較,每一趟都把最大或最小的數浮出
(2)優化:設置一個boolean?isOrdered ;
? ?在第一輪排序中一旦a[j] > a[j+1],就把isOrdered設為false,否則isOrdered設為true,然后我們在每趟排序前檢查isOrdered,一旦發現它為false,即認為排序已完成。
(3)時間復雜度:要進行的比較次數為: (n-1) + (n-2) + ... + 1 = n*(n-1) / 2,因此冒泡排序的時間復雜度為O(n^2)。
? 最好的為O(n):數組已經有序,比較次數為n-1 ??
public static void bubbleSort(int[] arr) {int i,j,temp;boolean isOrdered = true;for(i=0;i<arr.length-1;i++) {for(j=0;j<arr.length-i-1;j++) {if(arr[j]>arr[j+1]) {temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;isOrdered = false;}}if(!isOrdered) {break;}}}2、選擇排序算法
(1)思想:用第一個數與其他數進行比較,選出序列中最小的數;把最小的數與第一個數交換位置;依次進行
(2)時間復雜度:要進行的比較次數為: (n-1) + (n-2) + ... + 1 = n*(n-1) / 2,因此選擇排序的時間復雜度為O(n^2)。
public static void selectSort(int[] a) {int N = a.length;for (int i = 0; i < N - 1; i++) {int min = i;for (int j = i + 1; j < N; j++) {if (a[j] < a[min]) {min = j;}}exchange(a, i, min);}} public static void exchange(int[] a, int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}3、直接插入排序算法
(1)思想:在一組序列中,假設前面(n-1)[n>=2]個元素都已經拍好序,現在把第n個數插入到前面的有序序列中,使得這n個數都有序。
(2)分析:以抓牌過程來舉例,i為剛抓的牌的索引,i-1為我們剛排好的牌中的最后一張的索引,賦值給 j =?i - 1? 。在內層循環中,若a[ i ] < a[ j ],則 j--,
繼續向前比較,直到找到 a[ i ] < a[ j ],就會跳出內層循環,這時我們把a[i]插入到a[j]后(把大于a[ i ]的數往后挪一個位置),就把剛抓的牌插入
? 到已排好牌中的合適的位置了。重復以上過程就能完成待排序數組的排序。
(3)時間復雜度:在平均情況下以及最壞情況下,它的時間復雜度均為O(n^2),而在最好情況下(輸入數組完全有序),插入排序的時間復雜度能夠提升至O(N)
(4)適合場景 :插入排序對于部分有序的數組十分高效,也很適合小規模的數組。
public static void insertSort(int[] a) {int i, j;//可以想象是把數組分為兩邊,左邊是排好序的 j(0~i-1), 右邊是未排序的 i(1~a.length)for (i = 1; i < a.length; i++) { for (j = i - 1; j >= 0 && a[i] < a[j]; j--) {}// 將a[i]插入到a[j]后面,把大于a[i]的數往后挪一個位置 int temp = a[i];for (int k = i; k > j + 1; k--) {a[k] = a[k - 1];}a[j + 1] = temp;}}
4、希爾排序算法
(1)思想:先將待排序列按某個增量h分為若干組,然后對每個組中的子序列進行插入排序,縮小增量,再分組排序,當增量為1時,排序完成。
(2)分析:希爾排序是對直接插入排序的優化。? 實際上,h的取值序列的選取會影響到希爾排序的性能。引用結論:
使用遞增序列1, 4, 13, 40, 121, 364, ...的希爾排序所需的比較次數不會超過數組尺寸的若干倍乘以遞增序列的長度。即h = 3 * k + 1,其中k為[0, N/3)區間內的整數。(3)時間復雜度:在通常情況下,希爾排序的復雜度要比O(n^2)好得多。實際上,最壞情況下希爾排序所需要的比較次數與O(n^1.5)成正比,
在實際使用中,希爾排序要比插入排序和選擇排序、冒泡排序快得多。而且盡管待排序數組很大,希爾排序也不會比快速排序等高級算法慢很多。平均復雜度為O(nlogn)
public static void ShellSort(int[] a) {int N = a.length;int h=1;while(h<N/3) {h = h*3+1;}while(h>=1) {int n,i,j,k;//劃分為n個子序列,每個子序列進行插入排序for(n=0;n<h;n++) {for(i=n+h;i<N;i+=h) {for(j=i-h;j>=0&&a[j]>a[i];j-=h) {}int temp = a[i];//往后挪一個位置for(k=i;k>j+h;k-=h) {a[k]=a[k-h];}//騰出的位置復制給a[i]a[j+h] = temp;}}h=h/3;}}5、基礎算法的比較:希爾排序要比其他三種排序都快,而插入排序要比選擇排序、冒泡排序快,冒泡排序在實際執行性能最差。
二、高級算法
1、歸并排序算法
(1)思想:歸并排序的主要思想是:將待排序數組遞歸的分解成兩半,分別對它們進行排序,然后將結果“歸并”(遞歸的合并)起來。
?
(2)缺點:所需的額外空間與待排序數組的尺寸成正比,需要重新創建一個大小與原數組一樣的的數組來存放結果
(3)時間復雜度:歸并排序的時間復雜度為O(nlogn)
2、快速排序算法
快速排序是目前應用最廣泛的排序算法之一,它是一般場景中大規模數據排序的首選,它的實際性能要好于歸并排序。通常情況下,快速排序的時間復雜度為O(nlogn),但在最壞情況下它的時間復雜度會退化至O(n^2),除了實際執行性能好,快速排序的另一個優勢是它能夠實現“原地排序”,也就是說它幾乎不需要額外的空間來輔助排序。
(1)思想:選取一個基準點,分別從數組兩端想中間掃描數組。設置兩個標志low,high ;?
首先從后半部分開始往low方向掃描,如果發現元素值小于基準點,則交換low,high,
然后從前半部分向high方向開始掃描,如果發現元素值大于基準點的值,就換low,high位置,重復這兩步,知道low=high,完成一趟排序;
?然后遞歸左右子數組。 ?
(2)優化:
a、對于尺寸比較小的數組,插入排序要比快速排序快,因此當遞歸調用切分方法到切分所得數組已經很小時,我們不妨用插入排序來排序小數組。
b、三數取中:當輸入的數組已經有序時,每次劃分就只能使得待排序列減一,最壞情況下它的時間復雜度會退化至O(n^2)
(3)時間復雜度:通常情況下,快速排序的時間復雜度為O(nlogn)
//三數取中public static int SelectPivotMedianOfThree(int arr[], int start, int end) {int mid = (start + end) / 2;if (arr[end] < arr[start]) {swap(arr, start, end);}if (arr[end] < arr[mid]) {swap(arr, mid, end);}if (arr[start] < arr[mid]) {swap(arr, mid, start);}return arr[start];}private static void swap(int[] arr, int start, int end) {int temp = arr[start];arr[start] = arr[end];arr[end] = temp;}//完成一趟劃分 public static int partition(int[] a, int low, int high) {int key = SelectPivotMedianOfThree(a, low, high);while (low < high) {while (a[high] >= key && high > low) {high--;}a[low] = a[high];while (a[low] <= key && high > low) {low++;}a[high] = a[low];}a[high] = key;return high;}public static void fastSort(int[] a, int low, int high) {if (low >= high)
return;
//在這里可以使用插入排序進行優化 if(high <= low + size) insertSort(a,low ,high) size為一個常數,可以是5~15 int index = partition(a, low, high);fastSort(a, low, index - 1);fastSort(a, index + 1, high);}
3、堆排序算法
(1)思想:堆排序是將數據看成完全二叉樹,根據完全二叉樹的特性來進行排序的一種算法。堆分為大頂堆和小頂堆。
(2)時間復雜度:時間復雜度為O(nlogn)
(3)適用:堆排序是我們所知的唯一能夠同時最優地利用空間和時間的方法——在最壞情況下他也能保證~2NlgN次比較和恒定的額外空間。
public static void maxHeapDown(int[]a,int currentRootNode,int size) {int c = currentRootNode;int left = 2*c+1; //左孩子//進行根節點與左右孩子的比較for(;left<=size;c=left,left=left*2+1) {if(left<size && a[left]<a[left+1])//選出左右孩子中較大的一個left++;if(a[c]>=a[left]);//較大的孩子與父節點進行比較else {int temp = a[c];a[c] = a[left];a[left] = temp;}}}public static void heapSort(int[]a) {int n = a.length;int i,temp;//一趟排序,得到一個根元素最大的二叉堆for(i=n/2-1;i>=0;i--) {maxHeapDown(a, i, n-1);}for(i=n-1;i>0;i--) {temp = a[0];a[0] = a[i];a[i] = temp;maxHeapDown(a, 0, i-1);//縮小范圍a[1..n-1] } }4、計數排序算法
(1)思想:找出數組中的最大值和最小值;
? 創建另外一個數組c[min~max]來統計每個元素中出現的次數,并求前綴和;
創建臨時數組b來輸出結果,反向遍歷原數組a
?i = 8 , a[9] = 3 ; c[3] = 7; b[7] = 3, c[3]--
? ? ? ?
(2)適用:適用于元素值分布連續,跨度小的情況
(3)時間復雜度:O(n+k),k為數組中的最大值
?
5、桶排序算法
(1)桶排序的思想:
- 根據輸入建立適當個數的桶,每個桶可以存放某個范圍內的元素;
- 將落在特定范圍內的所有元素放入對應的桶中;
- 對每個非空的桶中元素進行排序,可以選擇通用的排序方法,比如插入、快排;
- 按照劃分的范圍順序,將桶中的元素依次取出。排序完成。
舉個例子,假如被排序的元素在0~99之間,我們可以建立10個桶,每個桶按范圍順序依次是[0, 10)、[10, 20]......[90, 99),注意是左閉右開區間。對于待排序數組[0, 3, 2, 80, 70, 75, 72, 88],[0, 3, 2]會被放到[0, 10)這個桶中,[70 ,75, 72]會被放到[70, 80)這個桶中,[80, 88]會被放到[80, 90)這個桶中,對這三個桶中的元素分別排序。得到
- [0, 10)桶中的元素: [0, 2, 3]
- [70, 80)桶中的元素: [70, 72, 75]
- [80, 90)桶中的元素: [80, 88]
依次取出三個桶中元素,得到序列[0, 2, 3, 70, 72, 75, 80, 88]已經排序完成。
可以用一個數組bucket[]存放各個桶,每個桶用鏈表表示,用于存放處于同一范圍內的元素。上面建立桶的方法是根據輸入范圍為0~99,建立了10個桶,每個桶可以裝入10個元素,這將元素分布得很均勻,在桶排序中保證元素均勻分布到各個桶尤為關鍵。
要注意:如何選擇桶的個數,以及使用哪個映射函數將元素轉換成桶的索引都是不一定的。
例如:
- 建立的桶個數與待排序數組個數相同,這個簡單的數字雖然大多數情況下會浪費許多空間(很多桶是空的),但也正因為桶的數量多,也很好地避免了大量元素都裝入同一個桶中的情況。
- 對于待排序數組中每個元素,使用如下映射函數將每個元素放到合適的桶中。這相當于每個桶能裝的元素個數為
- bucketIndex = (value * arr.length) / (maxValue + 1)
桶排序可以是穩定的。這取決于我們對每個桶中的元素采取何種排序方法,比如桶內元素的排序使用快速排序,那么桶排序就是不穩定的;如果使用的是插入排序,桶排序就是穩定的。桶排序和計數排序有個共同的缺點:耗費大量空間。
桶排序也不能很好地應對元素值跨度很大的數組。比如[3, 2, 1, 0 ,4, 8, 6, 999],按照上面的映射規則,999會放入一個桶中,剩下所有元素都放入同一個桶中,在各個桶中元素分布極不均勻,這就失去了桶排序的意義。
6、基數排序算法
(1)思想:將整數按位數切割成不同的數字,然后利用桶排序算法存儲和分別比較每個位數,并進行排序。
(2)實現:首先把序列統一長度,采取高位補0,然后從最低位開始,依次排序。
public static int get_max( int[] a) {int max=a[0];for(int i=1;i<a.length;i++) {if(a[i]>max)max = a[i];}return max;}public static void count_sort(int []a,int exp) {int i;int output[] = new int[a.length];//存儲結果int buckets[] = new int[10];//10個桶//統計頻數for(i=0;i<a.length;i++) {buckets[(a[i]/exp)%10]++;}//求前綴和,第一個沒有前綴,從第二個開始for(i=1;i<10;i++) {buckets[i] += buckets[i-1];}//反向遍歷for(i=a.length-1;i>=0;i--) {output[buckets[(a[i]/exp)%10]-1] = a[i];buckets[(a[i]/exp)%10]--;}for(i=0;i<a.length;i++)a[i] = output[i];}public static void RadixSort(int []a) {int max = get_max(a);int exp;for(exp=1;(max/exp)%10>0;exp*=10) {count_sort(a,exp);}}?
?
借鑒博客:https://www.cnblogs.com/sun-haiyu/p/7859252.html
? ? https://www.cnblogs.com/lizr-ithouse/p/5839384.html
https://www.cnblogs.com/guoyaohua/p/8600214.html
?
轉載于:https://www.cnblogs.com/dongtian-blogs/p/10763439.html
總結
以上是生活随笔為你收集整理的排序算法以及其java实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java数据结构——2-3树
- 下一篇: gcc 编译