資料結構學習筆記三(排序)
一、氣泡排序
氣泡排序只會操作相鄰的兩個資料。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關係要求。如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複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此排序之後,手機號碼就都有序了。