8大排序算法圖文講解


排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。

我們這里說說八大排序就是內部排序。

    

    當n較大,則應采用時間復雜度為O(nlog2n)的排序方法:快速排序、堆排序或歸並排序序。

   快速排序:是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短;

算法一:插入排序

 

插入排序示意圖

插入排序是一種最簡單直觀的排序算法,它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。

算法步驟:

1)將第一待排序序列第一個元素看做一個有序序列,把第二個元素到最后一個元素當成是未排序序列。

2)從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置。(如果待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的后面。)

代碼實現:

 1 public class InsertSort {
 2     
 3     public static void sort(int[] num){
 4         int i,j,min,temp;
 5         for(i=0;i<num.length;i++){
 6             min=i;//將當前下標定義為最小值下標
 7             for(j=i+1;j<num.length;j++){
 8                 if (num[min]>num[j]) {
 9                     min=j;//如果有小於當前最小值的關鍵字,將此關鍵字的下標賦值給min
10                 }
11             }
12             if (i!=min) {//若min不等於i,說明上面相互比較的為true,即有最小值,交換
13                 temp=num[i];
14                 num[i]=num[min];
15                 num[min]=temp;
16             }
17         }
18         for (int k : num) {
19             System.out.println(k);
20         }
21         
22     }
23     
24     
25     public static void main(String[] args) {
26         // TODO 自動生成的方法存根
27         int[] num={5,2,4,6,8,9,7,1,3,0};
28         sort(num);
29     }
30 }

 算法二:希爾排序

希爾排序示意圖

 

 

給定實例的shell排序的排序過程
假設待排序文件有10個記錄,其關鍵字分別是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次為:
5,3,1

希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。

希爾排序是基於插入排序的以下兩點性質而提出改進方法的:

  • 插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率
  • 但插入排序一般來說是低效的, 因為插入排序每次只能將數據移動一位

希爾排序的基本思想是:先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。

算法步驟:

1)選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;

2)按增量序列個數k,對序列進行k 趟排序;

3)每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。

代碼實現:

 1 public class ShellSort {
 2     //希爾排序
 3     public static void sort(int[] a) {
 4         // 希爾排序
 5         int d = a.length;
 6         while (true) {
 7             d = d / 2;
 8             for (int x = 0; x < d; x++) {
 9                 for (int i = x + d; i < a.length; i = i + d) {
10                     int temp = a[i];
11                     int j;
12                     for (j = i - d; j >= 0 && a[j] > temp; j = j - d) {
13                         a[j + d] = a[j];
14                     }
15                     a[j + d] = temp;
16                 }
17             }
18             if (d == 1) {
19                 break;
20             }
21         }
22         
23         for (int k : a) {
24             System.out.println(k);
25         }
26     }
27 
28     public static void main(String[] args) {
29         // TODO Auto-generated method stub
30         int[] a = { 5, 2, 4, 6, 8, 9, 7, 1, 3, 0 };
31         // int[]a={49,38,65,97,76,13,27,49,78,34,12,64,1};
32         sort(a);
33     }
34 
35 }

算法三:選擇排序

選擇排序示意圖

選擇排序(Selection sort)也是一種簡單直觀的排序算法。

算法步驟:

1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

2)再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。

3)重復第二步,直到所有元素均排序完畢。

代碼實現:

 1 /**
 2  * 選擇一個min做基准和其他的數據相互比較,如果比較的數大則把當前的數的賦值給min
 3  * 以此類推
 4  * @author Administrator
 5  *
 6  */
 7 public class SelectSort {
 8     //簡單選擇排序,選擇一個min做基准和其他的數據相互比較
 9     public static void sort(int[] num){
10         int i,j,min,temp;
11         for(i=0;i<num.length;i++){
12             min=i;//將當前下標定義為最小值下標
13             for(j=i+1;j<num.length;j++){
14                 if (num[min]>num[j]) {
15                     min=j;//如果有小於當前最小值的關鍵字,將此關鍵字的下標賦值給min
16                 }
17             }
18             if (i!=min) {//若min不等於i,說明上面相互比較的為true,即有最小值,交換
19                 temp=num[i];
20                 num[i]=num[min];
21                 num[min]=temp;
22             }
23         }
24         for (int k : num) {
25             System.out.println(k);
26         }
27         
28     }
29     
30     public static void main(String args[]){
31         int[] num={5,2,4,6,8,9,7,1,3,0};
32         sort(num);
33 
34     }
35 }

 算法四:冒泡排序

冒泡排序示意圖

 冒泡排序(Bubble Sort)也是一種簡單直觀的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。

算法步驟:

1)比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。

2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。這步做完后,最后的元素會是最大的數。

3)針對所有的元素重復以上的步驟,除了最后一個。

4)持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。

代碼實現:

 1 /**
 2  * 相鄰數據兩兩比較,大的排上面,小的排下面 第一次可排出最小的值
 3  * 第二次排出第二小的值
 4  * 第三次排出第三小的值
 5  * 以此類推排出順序
 6  * @author Administrator
 7  *
 8  */
 9 public class BubbleSort {
10     
11     //初級版
12     public static void sort1(int[] num){
13         int i,j,temp;
14         for(i=0;i<num.length;i++){
15             for(j=i+1;j<num.length;j++){
16                 if (num[i]>num[j]) {
17                     temp=num[i];
18                     num[i]=num[j];
19                     num[j]=temp;
20                 }
21             }
22         }
23         for (int k : num) {
24             System.out.println(k);
25         }
26         
27     }
28     //中級版
29     public static void sort2(int[] num){
30         int i,j,temp;
31         for(i=0;i<num.length;i++){
32             for(j=num.length-1;j>i;j--){
33                 if (num[j-1]>num[j]) {
34                     temp=num[j-1];
35                     num[j-1]=num[j];
36                     num[j]=temp;
37                 }
38             }
39         }
40         for (int k : num) {
41             System.out.println(k);
42         }
43         
44     }
45     //終極版
46     public static void sort3(int[] num){
47         int i,j,temp;
48         boolean flag=true;
49         for(i=0;i<num.length&&flag;i++){
50             flag=false;
51             for(j=num.length-1;j>i;j--){
52                 if (num[j-1]>num[j]) {
53                     temp=num[j-1];
54                     num[j-1]=num[j];
55                     num[j]=temp;
56                     flag=true;
57                 }
58             }
59         }
60         for (int k : num) {
61             System.out.println(k);
62         }
63         
64     }
65     
66     
67     public static void main(String args[]){
68         int[] num={5,2,4,6,8,9,7,1,3,0};
69 //        sort1(num);
70 //        sort2(num);
71         sort3(num);
72     }
73     
74 }

 算法五:歸並排序

歸並排序示意圖

歸並排序(MERGE-SORT)是利用歸並的思想實現的排序方法,該算法采用經典的分治(divide-and-conquer)策略(分治法將問題(divide)成一些小的問題然后遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。

分而治之

   可以看到這種結構很像一棵完全二叉樹,本文的歸並排序我們采用遞歸去實現(也可采用迭代的方式去實現)。階段可以理解為就是遞歸拆分子序列的過程,遞歸深度為log2n。

合並相鄰有序子序列

  再來看看階段,我們需要將兩個已經有序的子序列合並成一個有序序列,比如上圖中的最后一次合並,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合並為最終序列[1,2,3,4,5,6,7,8],來看下實現步驟。

代碼實現

復制代碼
package sortdemo;

import java.util.Arrays;

/**
 * Created by chengxiao on 2016/12/8.
 */
public class MergeSort {
    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++];
        }
    }
}
復制代碼

執行結果

[1, 2, 3, 4, 5, 6, 7, 8, 9]

最后

  歸並排序是穩定排序,它也是一種十分高效的排序,能利用完全二叉樹特性的排序一般性能都不會太差。java中Arrays.sort()采用了一種名為TimSort的排序算法,就是歸並排序的優化版本。從上文的圖中可看出,每次合並操作的平均時間復雜度為O(n),而完全二叉樹的深度為|log2n|。總的平均時間復雜度為O(nlogn)。而且,歸並排序的最好,最壞,平均時間復雜度均為O(nlogn)。

 算法六:快速排序

假如我們的計算機每秒鍾可以運行10億次,那么對1億個數進行排序,桶排序則只需要0.1秒,而冒泡排序則需要1千萬秒,達到115天之久,是不是很嚇人。那有沒有既不浪費空間又可以快一點的排序算法呢?那就是“快速排序”啦!光聽這個名字是不是就覺得很高端呢。
 
        假設我們現在對“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。想一想,你有辦法可以做到這點嗎?
 
        給你一個提示吧。請回憶一下冒泡排序,是如何通過“交換”,一步步讓每個數歸位的。此時你也可以通過“交換”的方法來達到目的。具體是如何一步步交換呢?怎樣交換才既方便又節省時間呢?先別急着往下看,拿出筆來,在紙上畫畫看。我高中時第一次學習冒泡排序算法的時候,就覺得冒泡排序很浪費時間,每次都只能對相鄰的兩個數進行比較,這顯然太不合理了。於是我就想了一個辦法,后來才知道原來這就是“快速排序”,請允許我小小的自戀一下(^o^)。
 

        方法其實很簡單:分別從初始序列“6  1  2 7  9  3  4  5 10  8”兩端開始“探測”。先從右往左找一個小於6的數,再從左往右找一個大於6的數,然后交換他們。這里可以用兩個變量i和j,分別指向序列最左邊和最右邊。我們為這兩個變量起個好聽的名字“哨兵i”和“哨兵j”。剛開始的時候讓哨兵i指向序列的最左邊(即i=1),指向數字6。讓哨兵j指向序列的最右邊(即j=10),指向數字8。

 
       首先哨兵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(N 2),它的平均時間復雜度為O(NlogN)。
代碼實現:
 1 public class QuickSort {
 2 
 3     public static void quickSort(int arr[], int _left, int _right) {
 4         int left = _left;
 5         int right = _right;
 6         int temp = 0;
 7         if (left <= right) { // 待排序的元素至少有兩個的情況
 8             temp = arr[left]; // 待排序的第一個元素作為基准元素
 9             while (left != right) { // 從左右兩邊交替掃描,直到left = right
10 
11                 while (right > left && arr[right] >= temp)
12                     right--; // 從右往左掃描,找到第一個比基准元素小的元素
13                 arr[left] = arr[right]; // 找到這種元素arr[right]后與arr[left]交換
14 
15                 while (left < right && arr[left] <= temp)
16                     left++; // 從左往右掃描,找到第一個比基准元素大的元素
17                 arr[right] = arr[left]; // 找到這種元素arr[left]后,與arr[right]交換
18 
19             }
20             arr[right] = temp; // 基准元素歸位
21             quickSort(arr, _left, left - 1); // 對基准元素左邊的元素進行遞歸排序
22             quickSort(arr, right + 1, _right); // 對基准元素右邊的進行遞歸排序
23         }
24     }
25 
26     public static void main(String[] args) {
27         int array[] = { 10, 5, 3, 1, 7, 2, 8 };
28         System.out.println("排序之前:");
29         for (int element : array) {
30             System.out.print(element + " ");
31         }
32 
33         quickSort(array, 0, array.length - 1);
34 
35         System.out.println("\n排序之后:");
36         for (int element : array) {
37             System.out.print(element + " ");
38         }
39 
40     }
41 
42 }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM