做爲程序員,時時刻刻都要與算法打交道,其中排序算法算是比較常見的一種。而在面試程序員崗位中, 不出意外,排序算法也是比較常見的考量之一。所以咱們有必要了解和掌握各類常見排序算法。
這個篇文章記錄了幾種常見的排序算法,並各類排序算法極端狀況的優劣,供學習和參考。java
對數據進行排序意味着以特定順序排列數據,一般是在相似數組的數據結構中。您可使用各類排序標準,常見的排序標準是從最小到最大排序數字,反之亦然,或按字典順序排序字符串。程序員
常見的幾種排序算法面試
冒泡排序經過交換相鄰元素(若是它們不是所需的順序)來工做。此過程從數組的開頭重複,直到全部元素都按順序排列。算法
舉例 對4 2 1 5 3進行排序:api
2 1 4 3 5 :這是一次迭代後獲得的數組。 由於在第一次傳遞期間至少發生了一次交換(實際上有三次),咱們須要再次遍歷整個數組並重復相同的過程。 經過重複這個過程,直到再也不進行交換,咱們將有一個排序的數組。數組
代碼實現數據結構
public void bubbleSort(int[] array) {
boolean sorted = false;
int temp;
while(!sorted) {
sorted = true;
for (int i = 0; i < array.length - 1; i++) {
if (a[i] > a[i+1]) {
temp = a[i];
a[i] = a[i+1];
a[i+1] = temp;
sorted = false;
}
}
}
}
複製代碼
選擇排序是每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到所有待排序的數據元素排完性能
舉例 對4 2 1 5 3進行排序:學習
至此排序結束。測試
代碼實現
public void selectionSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int min = array[i];
int minId = i;
for (int j = i+1; j < array.length; j++) {
if (array[j] < min) {
min = array[j];
minId = j;
}
}
// 交換
int temp = array[i];
array[i] = min;
array[minId] = temp;
}
}
複製代碼
插入排序是將數組劃分爲已排序和未排序的子數組。 排序部分的開頭長度爲1,對應於數組中的第一個(最左側)元素。咱們遍歷數組,在每次迭代中,咱們將數組的排序部分擴展一個元素。 在擴展時,咱們將新元素放置在已排序子陣列中的適當位置。咱們經過將全部元素向右移動直到遇到咱們沒必要移動的第一個元素來實現這一點。
舉例 對3 5 7 8 4 2 1 9 6進行排序:
在這個過程以後,排序的部分被一個元素擴展,咱們如今有五個而不是四個元素。每次迭代都會這樣作,最後咱們將對整個數組進行排序。
代碼實現
public void insertionSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int current = array[i];
int j = i - 1;
while(j >= 0 && current < array[j]) {
array[j+1] = array[j];
j--;
}
array[j+1] = current;
}
}
複製代碼
合併排序/歸併算法使用遞歸來解決比先前提出的算法更有效的排序問題,特別是它使用分而治之的方法。 使用這兩個概念,咱們將整個數組分解爲兩個子數組,而後: 一、對數組的左半部分進行排序(遞歸) 二、對數組的右半部分進行排序(遞歸) 三、合併解決方案
舉例 對3 5 4 2 1 進行排序:
此樹表示遞歸調用的工做方式。標有向下箭頭的數組是咱們須要排序的數組,而向上箭頭的數組是咱們合併完後的數據。因此先按下遞歸向下箭頭數組到樹的底部,而後向上返回併合並。在咱們的例子中,咱們有數組3 5 3 2 1,因此咱們把它分紅3 5 4和2 1。爲了對它們進行排序,咱們進一步將它們分紅它們的組 一旦咱們到達底部,咱們就開始合併並按照咱們的方式對它們進行排序
代碼實現
public void mergeSort(int[] array, int left, int right) {
if (right <= left) return;
int mid = (left+right)/2;
mergeSort(array, left, mid);
mergeSort(array, mid+1, right);
merge(array, left, mid, right);
}
private void merge(int[] array, int left, int mid, int right) {
// 計算長度
int lengthLeft = mid - left + 1;
int lengthRight = right - mid;
// 建立臨時數組
int leftArray[] = new int [lengthLeft];
int rightArray[] = new int [lengthRight];
// 將須要排序的數組分別傳入左右兩個臨時數組裏
for (int i = 0; i < lengthLeft; i++)
leftArray[i] = array[left+i];
for (int i = 0; i < lengthRight; i++)
rightArray[i] = array[mid+i+1];
// 左右臨時數組當前索引
int leftIndex = 0;
int rightIndex = 0;
// 將左右臨時數組從新按從小到大順序寫入待排序數組中
for (int i = left; i < right + 1; i++) {
// 若是R和L中仍然有未複製的元素,則複製這兩個元素的最小值
if (leftIndex < lengthLeft && rightIndex < lengthRight) {
if (leftArray[leftIndex] < rightArray[rightIndex]) {
array[i] = leftArray[leftIndex];
leftIndex++;
}
else {
array[i] = rightArray[rightIndex];
rightIndex++;
}
}
// 若是全部元素都已從rightArray複製,則複製left Array的其他部分
else if (leftIndex < lengthLeft) {
array[i] = leftArray[leftIndex];
leftIndex++;
}
// 若是全部元素都已從leftArray複製,則複製right Array的其他部分
else if (rightIndex < lengthRight) {
array[i] = rightArray[rightIndex];
rightIndex++;
}
}
}
複製代碼
經過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。
舉例 對6 1 2 7 9 3 4 5 10 8 進行排序: 首先取基準數, 每一輪的排序要義是將數組以基準數爲準,將數組分爲左右兩個部分, 左邊部分都比基準數小, 右邊部分都比基準數大。
至此, 第一輪搜索結束, 基準數6將數組分割成兩部分, 接下來,將這兩部分按照1-8步驟, 分別進行排序, 直至沒法切分,則排序完成。
摘用網上圖片:
代碼實現
public void quickSort(int[] array, int begin, int end) {
if (end <= begin) return;
int pivot = partition(array, begin, end);
quickSort(array, begin, pivot-1);
quickSort(array, pivot+1, end);
}
private int partition(int[] array, int begin, int end) {
int baseValue = array[begin];
int leftIndex = begin + 1;
int rightIndex = end;
int temp;
while(leftIndex != rightIndex) {
//先從左邊向右搜索
while(array[leftIndex] <= baseValue && leftIndex < rightIndex) {
leftIndex ++;
}
//再從右邊向左搜索
while(rightIndex] >= baseValue && leftIndex < rightIndex) {
rightIndex --;
}
//交換兩個數的位置
if (leftIndex < rightIndex) {
temp = array[leftIndex];
array[leftIndex] = array[rightIndex];
array[rightIndex] = temp;
}
}
//最終將基準數歸位
array[begin] = array[leftIndex];
array[leftIndex] = baseValue;
//返回基準數位置
return leftIndex;
}
複製代碼
堆排序是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點
堆排序的基本思路:
代碼實現
public void heapSort(int[] array) {
if (array.length == 0) return;
// 構建大頂堆
int length = array.length;
for (int i = length / 2-1; i >= 0; i--) {
heapify(array, length, i);
}
for (int i = length-1; i >= 0; i--) {
int temp = array[0];
array[0] = array[i];
array[i] = temp;
//從新構建大頂堆
heapify(array, i, 0);
}
}
private void heapify(int[] array, int length, int i) {
int leftChild = 2*i+1;
int rightChild = 2*i+2;
int largest = i;
// 若是左節點大於父節點
if (leftChild < length && array[leftChild] > array[largest]) {
largest = leftChild;
}
// 若是右節點大於父節點
if (rightChild < length && array[rightChild] > array[largest]) {
largest = rightChild;
}
// 若是須要則交換
if (largest != i) {
int temp = array[i];
array[i] = array[largest];
array[largest] = temp;
heapify(array, length, largest);
}
}
複製代碼
名稱 | 最好狀況 | 平均狀況 | 最壞狀況 | 額外空間 | 穩定性 |
---|---|---|---|---|---|
冒泡排序 | n | n^2 | n^2 | 1 | 穩定 |
選擇排序 | n^2 | n^2 | n^2 | 1 | 不穩定 |
插入排序 | n | n^2 | n^2 | 1 | 穩定 |
歸併排序 | nlog n | nlog n | nlog n | n | 穩定 |
快速排序 | nlog n | nlog n | n | nlog n | 不穩定 |
插入排序 | n | nlog n | nlog n | 1 | 不穩定 |
在10,000個整數的隨機數組副本上,以上訴幾種算法進行測試,結果以下:
時間(NS) | 冒泡排序 | 插入排序 | 選擇排序 | 歸併 | 堆排序 | 快速排序 |
---|---|---|---|---|---|---|
第一次運行 | 266089476 | 21973989 | 66603076 | 5511069 | 5283411 | 4156005 |
第二輪 | 323692591 | 29138068 | 80963267 | 8075023 | 6420768 | 7060203 |
第三次運行 | 303853052 | 21380896 | 91810620 | 7765258 | 8009711 | 7622817 |
第四次運行 | 410171593 | 30995411 | 96545412 | 6560722 | 5837317 | 2358377 |
第五次運行 | 315602328 | 26119110 | 95742699 | 5471260 | 14629836 | 3331834 |
第六次運行 | 286841514 | 26789954 | 90266152 | 9898465 | 4671969 | 4401080 |
第七次運行 | 384841823 | 18979289 | 72569462 | 5135060 | 10348805 | 4982666 |
八跑 | 393849249 | 34476528 | 107951645 | 8436103 | 10142295 | 13678772 |
第九跑 | 306140830 | 57831705 | 138244799 | 5154343 | 5654133 | 4663260 |
第十次運行 | 306686339 | 34594400 | 89442602 | 5601573 | 4675390 | 3148027 |
僅以此樣本爲例, 冒泡排序在性能方面是最差的, 堆排和快排性能最佳
對數據集進行排序是一種很是常見的操做,不管是進一步分析它們,仍是使用依賴於排序數據的更有效算法來加速搜索,過濾數據等。
歡迎長按下圖關注公衆號: 終身幼稚園