1. 程式人生 > >資料結構學習筆記三(排序)

資料結構學習筆記三(排序)

一、氣泡排序

氣泡排序只會操作相鄰的兩個資料。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關係要求。如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複n次,就完成了n個數據的排序工作。

//氣泡排序,n表示陣列中元素個數
public void bubbleSort(int[] a,int n){
        if(n<=1)
            return ;
        for(int i=0;i<n;++i){//有n個元素一共要進行n趟
            //通過交換相鄰的兩個元素,使其處於正確的排序位置
            for(int j=0;j<n-i-1;++j){    //剩下的沒有排序的元素個數
                if(a[j]>a[j+1]){
                    int temp=a[j];
                    a[j]=a[j+1];
                    a[j+1]=temp;
                }
            }
        }
    }

二、插入排序

將需要排序的陣列分為兩個區間,已排序區間和未排序區間。初始已排序區間只有一個元素,就是陣列的第一個元素。插入演算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間資料一直有序。重複這個過程,直到未排序區間中元素為空,演算法結束。
插入排序包含兩種操作,一種是元素的比較,一種是元素的移動。當我們需要將一個數據a插入到已排序區間時,需要拿a與已排序區間的元素依次比較大小,找到合適的插入位置。找到插入點之後,還需要將插入點之後的元素順序往後移動一位,這樣才能騰出位置給元素a插入。

/*
    插入排序
    把要排序的陣列分成兩個區間,已排序區間和未排序區間
    從未排序區間中取出元素插入到已i排序區間的合理位置
     */
public void insertionSort(int[] a,int n){
        if(n<=1)
            return;
        for(int i=1;i<n;i++){
            int value=a[i];    //未排序元素區間
            int j=i-1;         //已排序元素區間
            //查詢要插入的位置並移動資料
            for(;j>=0;--j){
                if(a[j]>value){
                    a[j+1]=a[j];
                }
                else{
                    break;
                }
            }
            a[j+1]=value;    //--j,邊界條件j為-1時才停
        }
    } 

三、為什麼插入排序比氣泡排序受歡迎

氣泡排序和插入排序的時間複雜度都是O(n²),都是原地排序演算法。它們之間的區別在於賦值操作。

            //氣泡排序
               if(a[j]>a[j+1]){
                    int temp=a[j];
                    a[j]=a[j+1];
                    a[j+1]=temp;
                }
             //插入排序
              if(a[j]>value){
                    a[j+1]=a[j];
                }

從實現程式碼上看,氣泡排序的資料交換要比插入排序的資料移動要複雜,氣泡排序需要三個賦值操作,而插入排序只需要一個。

四、選擇排序

選擇排序演算法的實現類似於插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其防到已排序區間的末尾。

//選擇排序
//把陣列分為已排序和未排序區間,每次從未排序區間中取出最小值,放到已排序區間末尾
public void selectionSort(int[] a,int n){
        if(n<=1)
            return;
        for(int i=0;i<n-1;++i){
            int minIndex=i;
            for(int j=i+1;j<n-1;++j){
                if(a[j]<a[minIndex])
                    minIndex=j;
            }
            //交換
            int temp=a[i];
            a[i]=a[minIndex];
            a[minIndex]=temp;
        }
    }

五、三種排序的比較

是原地排序? 是否穩定? 最好 最壞 平均
氣泡排序 O(n) O(n²) O(n²)
插入排序 O(n) O(n²) O(n²)
選擇排序排序 × O(n²) O(n²) O(n²)

上述三種演算法適合對於小規模資料的排序,用起來非常高效。不適合大規模資料的排序,時間複雜度太高。

六、歸併排序和快速排序

歸併排序和快速排序用的都是分治的思想,程式碼都是通過遞迴來實現,過程非常相似。歸併排序在任何情況下時間複雜度都比較穩定的排序演算法,但由於歸併排序不是原地排序演算法,空間複雜度比較高,是O(n)。正因為此,它沒有快排應用廣泛。

七、桶排序

桶排序的核心思想是將要排序的資料分到幾個有序的桶裡,每個桶裡的資料再單獨進行排序。桶內排完序之後,再把每個桶裡的資料按照順序依次取出,組成的序列就是有序的。桶排序的時間複雜度是O(n)。桶排序比較適合用在外部排序中。
桶排序應用:
比如我們有10GB的訂單資料,我們希望按訂單金額進行排序,但是記憶體有限,只有幾百MB,沒辦法一次性把10GB的資料都載入到記憶體中,這時候就可以使用桶排序。我們先掃描一遍檔案,看訂單金額所處的資料範圍。假設經過掃描我們瞭解到,訂單金額的最小值為1元,最大值為10萬元。我們將所有訂單根據金額劃分到100個桶裡,第一個桶儲存金額在1元到1000的訂單,第二個桶儲存金額在1001到2000元的訂單,一次類推。每一個桶對應一個檔案,並且按照金額的大小順序編號命名。理想情況下,如果訂單金額在1到10萬之間順序分佈,那訂單會被均勻劃分到100個桶檔案中,每個小檔案中儲存大約100MB的訂單資料,我們可以將這100個小檔案依次防到記憶體中,用快排來排序。等所有檔案都排好序後,我們只需要按照檔案編號,從小到大依次讀取每個小檔案中的訂單資料,並將其寫入到一個檔案中,這樣就完成了排序。如果劃分後記憶體仍舊放不下,可以進一步進行劃分。

八、基數排序

我們有10萬個手機號碼,希望將這10萬個手機號碼從小到大排序,可以使用基數排序。先按照最後一位來排序手機號碼,然後再按倒數第二位重新排序,以此類推,最後按照第一位進行排序,經過11此排序之後,手機號碼就都有序了。