資料結構複習之【排序】

來源:互聯網
上載者:User
文章目錄
  • 預備:最簡單的排序
  • 1.標準冒泡排序
  • 2.改進冒泡排序
  • 最佳化方案

排序:對一序列對象根據某個關鍵字進行排序;

穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面;

不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面;

 

內排序:所有排序操作都在記憶體中完成;

外排序:由於資料太大,因此把資料放在磁碟中,而排序通過磁碟和記憶體的資料轉送才能進行;

 

排序耗時的操作:比較、移動;

排序分類:

(1)交換類:冒泡排序、快速排序;此類的特點是通過不斷的比較和交換進行排序;

(2)插入類:簡單插入排序、希爾排序;此類的特點是通過插入的手段進行排序;

(3)選擇類:簡單選擇排序、堆排序;此類的特點是看準了再移動;

(4)歸併類:歸併排序;此類的特點是先分割後合并;

 

曆史進程:一開始排序演算法的複雜度都在O(n^2),希爾排序的出現打破了這個僵局;

 

以下視頻是Sapientia University創作的,用跳舞的形式示範排序步驟,這些視頻就可以當作複習排序的資料~

冒泡排序視頻:http://v.youku.com/v_show/id_XMzMyOTAyMzQ0.html

選擇排序視頻:http://v.youku.com/v_show/id_XMzMyODk5MDI0.html

插入排序視頻:http://v.youku.com/v_show/id_XMzMyODk3NjI4.html

希爾排序視頻:http://v.youku.com/v_show/id_XMzMyODk5MzI4.html

歸併排序視頻:http://v.youku.com/v_show/id_XMzMyODk5Njg4.html

快速排序視頻:http://v.youku.com/v_show/id_XMzMyODk4NTQ4.html

 

上面介紹的排序演算法都是基於排序的,還有一類演算法不是基於比較的排序演算法,即計數排序、基數排序

預備:最簡單的排序

此種實現方法是最簡單的排序實現;

缺點是每次找最小值都是單純的找,而沒有為下一次尋找做出鋪墊;

演算法如下:

 

public static int[] simple_sort(int[] arr) {for (int i = 0; i < arr.length; i++) {for (int j = i + 1; j < arr.length; j++) {if (arr[i] > arr[j]) {swap(arr, i, j);}}}return arr;}

一、冒泡排序

冒泡排序相對於最簡單的排序有了改進,即每次交換都是對後續有協助的,大數將會越來越大,小的數將會越來越小;

冒泡排序思想:兩兩相鄰元素之間的比較,如果前者大於後者,則交換

因此此排序屬於交換排序一類,同類的還有現在最常用的排序方法:快速排序;

1.標準冒泡排序

此種方法是最一般的冒泡排序實現,思想就是兩兩相鄰比較並交換;

演算法實現如下:

public static int[] bubble_sort2(int[] arr) {for (int i = 0; i < arr.length; i++) {for (int j = arr.length - 1; j > i; j--) {if (arr[j] < arr[j - 1]) {swap(arr, j, j - 1);}}}return arr;}

2.改進冒泡排序

改進在於如果出現一個序列,此序列基本是排好序的,如果是標準的冒泡排序,則還是需要進行不斷的比較;

改進方法:通過一個boolean isChanged,如果一次迴圈中沒有交換過元素,則說明已經排好序;

演算法實現如下:

 

// 最好:n-1次比較,不移動,因此時間複雜度為O(n),不佔用輔助空間// 最壞:n(n-1)/2次比較和移動,因此O(n^2),佔用交換的臨時空間,大小為1;public static int[] bubble_sort3(int[] arr) {boolean isChanged = true;for (int i = 0; i < arr.length && isChanged; i++) {isChanged = false;for (int j = i + 1; j < arr.length; j++) {if (arr[i] > arr[j]) {swap(arr, i, j);isChanged = true;}}}return arr;}

二、簡單選擇排序

簡單選擇排序特點:每次迴圈找到最小值,並交換,因此交換次數始終為n-1次;

相對於最簡單的排序,對於很多不必要的交換做了改進,每個迴圈不斷比較後記錄最小值,只做了一次交換(當然也可能不交換,當最小值已經在正確位置)

演算法如下:

 

//最差:n(n-1)/2次比較,n-1次交換,因此時間複雜度為O(n^2)//最好:n(n-1)/2次比較,不交換,因此時間複雜度為O(n^2)//好於冒泡排序public static int[] selection_sort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {int min = i;for (int j = i + 1; j < arr.length; j++) {if (arr[min] > arr[j]) {min = j;}}if (min != i)swap(arr, min, i);}return arr;}

 

三、簡單插入排序

思想: 給定序列,存在一個分界線,分界線的左邊被認為是有序的,分界線的右邊還沒被排序,每次取沒被排序的最左邊一個和已排序的做比較,並插入到正確位置;我們預設索引0的子數組有序;每次迴圈將分界線右邊的一個元素插入有序數組中,並將分界線向右移一位;

演算法如下:

 

// 最好:n-1次比較,0次移動 ,時間複雜度為O(n)// 最差:(n+2)(n-1)/2次比較,(n+4)(n-1)/2次移動,時間複雜度為 O(n^2)public static int[] insertion_sort(int[] arr) {int j;for (int i = 1; i < arr.length; i++) {if (arr[i] < arr[i - 1]) {int tmp = arr[i];for (j = i - 1; j >= 0 && arr[j] > tmp; j--) {arr[j + 1] = arr[j];}arr[j + 1] = tmp;}}return arr;}

簡單插入排序比選擇排序和冒泡排序好!

 

四、希爾排序

1959年Shell發明;

第一個突破O(n^2)的排序演算法;是簡單插入排序的改進版;

思想:由於簡單插入排序對於記錄較少或基本有序時很有效,因此我們可以通過將序列進行分組排序使得每組容量變小,再進行分組排序,然後進行一次簡單插入排序即可;

這裡的分組是跳躍分組,即第1,4,7位置為一組,第2,5,8位置為一組,第3,6,9位置為一組;

索引

1

2

3

4

5

6

7

8

9

此時,如果increment=3,則i%3相等的索引為一組,比如索引1,1+3,1+3*2

一般增量公式為:increment = increment/3+1;

演算法實現如下:

 

// O(n^(3/2))//不穩定排序演算法public static int[] shell_sort(int[] arr) {int j;int increment = arr.length;do {increment = increment / 3 + 1;for (int i = increment; i < arr.length; i++) { //i=increment 因為插入排序預設每組的第一個記錄都是已排序的if (arr[i] < arr[i - increment]) {int tmp = arr[i];for (j = i - increment; j >= 0 && arr[j] > tmp; j -= increment) {arr[j + increment] = arr[j];}arr[j + increment] = tmp;}}} while (increment > 1);return arr;}

五、堆排序

 

Floyd和Williams在1964年發明;

大根堆:任意父節點都比子節點大;

小根堆:任意父節點都比子節點小;

不穩定排序演算法,是簡單選擇排序的改進版;

思想:構建一棵完全二叉樹,首先構建大根堆,然後每次都把根節點即最大值移除,並用編號最後的節點替代,這時數組長度減一,然後重新構建大根堆,以此類推;

注意:此排序方法不適用於個數少的序列,因為初始構建堆需要時間;

演算法實現如下:

 

        // 時間複雜度為O(nlogn) //不穩定排序演算法//輔助空間為1//不適合排序個數較少的序列public static int[] heap_sort(int[] arr) {int tmp[] = new int[arr.length + 1];tmp[0] = -1;for (int i = 0; i < arr.length; i++) {tmp[i + 1] = arr[i];}// 構建大根堆:O(n)for (int i = arr.length / 2; i >= 1; i--) {makeMaxRootHeap(tmp, i, arr.length);}// 重建:O(nlogn)for (int i = arr.length; i > 1; i--) {swap(tmp, 1, i);makeMaxRootHeap(tmp, 1, i - 1);}for (int i = 1; i < tmp.length; i++) {arr[i - 1] = tmp[i];}return arr;}private static void makeMaxRootHeap(int[] arr, int low, int high) {int tmp = arr[low];int j;for (j = 2 * low; j <= high; j*=2) {if (j < high && arr[j] < arr[j + 1]) {j++;}if (tmp >= arr[j]) {break;}arr[low] = arr[j];low = j;}arr[low] = tmp;}

六、歸併排序

穩定排序演算法;

思想:利用遞迴進行分割和合并,分割直到長度為1為止,並在合并前保證兩序列原本各自有序,合并後也有序;

實現代碼如下:

 

// 穩定排序;// 時間複雜度O(nlogn)// 空間複雜度:O(n+logn)public static int[] merge_sort(int[] arr) {Msort(arr, arr, 0, arr.length - 1);return arr;}private static void Msort(int[] sr, int[] tr, int s, int t) {int tr2[] = new int[sr.length];int m;if (s == t) {tr[s] = sr[s];} else {m = (s + t) / 2;Msort(sr, tr2, s, m);Msort(sr, tr2, m + 1, t);Merge(tr2, tr, s, m, t);}}private static void Merge(int[] tr2, int[] tr, int i, int m, int t) {int j, k;for (j = i, k = m + 1; i <= m && k <= t; j++) {if (tr2[i] < tr2[k]) {tr[j] = tr2[i++];} else {tr[j] = tr2[k++];}}while (i <= m) {tr[j++] = tr2[i++];}while (k <= t) {tr[j++] = tr2[k++];}}

七、快速排序

冒泡排序的升級版;現在用的最多的排序方法;

思想:選取pivot,將pivot調整到一個合理的位置,使得左邊全部小於他,右邊全部大於他;

注意:如果序列基本有序或序列個數較少,則可以採用簡單插入排序,因為快速排序對於這些情況效率不高;

實現代碼如下:

        // 不穩定排序演算法// 時間複雜度:最好:O(nlogn) 最壞:O(n^2)// 空間複雜度:O(logn)public static int[] quick_sort(int[] arr) {qsort(arr, 0, arr.length - 1);return arr;}private static void qsort(int[] arr, int low, int high) {int pivot;if (low < high) {pivot = partition(arr, low, high);qsort(arr, low, pivot);qsort(arr, pivot + 1, high);}}private static int partition(int[] arr, int low, int high) {int pivotkey;pivotkey = arr[low];//選擇pivot,此處可以最佳化while (low < high) {while (low < high && arr[high] >= pivotkey) {high--;}swap(arr, low, high);//交換,此處可以最佳化while (low < high && arr[low] <= pivotkey) {low++;}swap(arr, low, high);}return low;}

最佳化方案

(1)選取pivot:選取pivot的值對於快速排序至關重要,理想情況,pivot應該是序列的中間數;

而前面我們只是簡單的取第一個數作為pivot,這點可以進行最佳化;

最佳化方法:抽多個數後取中位元作為pivot;

(2)對於小數組使用插入排序:因為快速排序適合大數組排序,如果是小數組,則效果可能沒有簡單插入排序來得好;

如果想進行最佳化,則可以使用以下代碼:

public static int[] quick_sort(int[] arr) {if(arr.length>10){qsort(arr, 0, arr.length - 1);}else{insertion_sort(arr);}return arr;}

八、計數排序

計數排序是典型的不是基於比較的排序演算法,基於比較的排序演算法最少也要O(nlogn),有沒有可能創造線性時間的排序演算法呢?那就是不基於比較的排序演算法;

如果數組的資料範圍為0~100,則很適合此演算法;

複雜度: O(n+k), n為原數組長度,k為資料範圍;


思想:


(1)首先找出數組中的最大值,然後建立一個計數數組(用來記錄每個元素的數量),長度為max,比如數組為{1,1,2,3,4,5},則建立一個長度為6的數組count[],count[1]存放數值1出現的次數,即2;

(2)填充count數組,即遍曆原數組,並且count[arr[i]-1]++;

(3)對count數組進行累加,即count[i] = count[i] + count[i-1];

(4)反向填充result數組,result[count[arr[i]]-1] = arr[i];


代碼如下:

import java.util.ArrayList;import java.util.Scanner;/** * 計數排序適用於: * (1)資料範圍較小,建議在小於1000 * (2)每個數值都要大於等於0 * @author xiazdong * */public class Count_Sort {public static void main(String[] args) {int[] array = readArray();System.out.print("排序前數組為:");print(array);int result[] = count_sort(array);System.out.print("排序後數組為:");print(result);}//讀取數組函數private static int[] readArray() {Scanner in = new Scanner(System.in);ArrayList<Integer> list = new ArrayList<Integer>();while(true){System.out.print("輸入數字:");int element = in.nextInt();if(element==-1){break;}else{list.add(element);}}Integer[] arr = list.toArray(new Integer[0]);int[]array = new int[arr.length];for(int i=0;i<arr.length;i++){array[i] = arr[i];}return array;}//計數排序public static int[] count_sort(int arr[]){int gap = findGap(arr);int[] count = new int[gap];int[] result = new int[arr.length];for(int i=0;i<arr.length;i++){count[arr[i]]++;}for(int i=1;i<count.length;i++){count[i] = count[i] + count[i-1];}//反向填充結果數組for(int i=arr.length-1;i>=0;i--){result[count[arr[i]]-1] = arr[i]; count[arr[i]]--;}return result;}public static void print(int result[]){for(int a:result){System.out.print(a+" ");}System.out.println();}/** * 找出數組的資料範圍,即最大數的值 * @param arr * @return */private static int findGap(int[] arr) {int max = arr[0];for(int i=1;i<arr.length;i++){if(max<arr[i]){max = arr[i];}}return (max+1);}}

九、基數排序

基數排序也是非比較的排序演算法,對每一位進行排序,從最低位開始排序,複雜度為O(kn),為數組長度,k為數組中的數的最大的位元;

比如{987,789} ,先通過個位元排序:{987,789},再通過十位元排序:{987,789},再通過百位元排序:{789,987}


思想:

(1)取得數組中的最大數,並取得位元;

(2)arr為原始數組,從最低位開始取每個位組成radix數組;

(3)對radix進行計數排序(利用計數排序適用於小範圍數的特點);


import java.util.ArrayList;import java.util.Scanner;/** * 計數排序適用於: * (1)資料範圍較小,建議在小於1000 * (2)每個數值都要大於等於0 * @author xiazdong * */public class Count_Sort {public static void main(String[] args) {int[] array = new int[]{1046,2084,9046,12074,56,7026,8099,17059,33,1};System.out.print("排序前數組為:");print(array);int result[] = radix_sort(array);System.out.print("排序後數組為:");print(result);}//基數排序 O(kn) public static int[] radix_sort(int[]arr){int radix[] = new int[arr.length];int count = 1;int n = findMaxLength(arr);for(int i=0;i<n;i++){radix = getRadix(arr,count);arr = count_sort(arr, radix);count *=10;}return arr;}private static int findMaxLength(int[] arr) {int max = arr[0];for(int i=1;i<arr.length;i++){if(max<arr[i]){max = arr[i];}}int count = 1;int mcount = 1;while((max / mcount)!=0){mcount = 1;count++;for(int i=0;i<count;i++){mcount *=10;}}return count;}//取得需要排序的位的數組private static int[] getRadix(int[] arr,int count) {//O(n)int radix[] = new int[arr.length];for(int i=0;i<arr.length;i++){radix[i] = arr[i]/count % 10;}return radix;}//類似計數排序//arr為原始數組//radix為需要排序的位的數組public static int[] count_sort(int arr[],int radix[]){int gap = findGap(radix);int[] count = new int[gap];int[] result = new int[radix.length];for(int i=0;i<radix.length;i++){count[radix[i]]++;}for(int i=1;i<count.length;i++){count[i] = count[i] + count[i-1];}//反向填充結果數組for(int i=radix.length-1;i>=0;i--){result[count[radix[i]]-1] = arr[i]; count[radix[i]]--;}return result;}public static void print(int result[]){for(int a:result){System.out.print(a+" ");}System.out.println();}/** * 找出數組的資料範圍,即最大數的值 * @param arr * @return */private static int findGap(int[] arr) {int max = arr[0];for(int i=1;i<arr.length;i++){if(max<arr[i]){max = arr[i];}}return (max+1);}}

對比圖

此圖摘自http://www.cnblogs.com/cj723/archive/2011/04/29/2033000.html的圖

總結:每個排序都有每個排序的優點,我們需要在適當的時候用適當的演算法;

比如在基本有序、數組規模小時用直接插入排序;

比如在大數組時用快速排序;

比如如果要想穩定性,則使用歸併排序;

摘錄維基百科圖片:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.