文章和資源同步更新至微信公衆號:算法工程師之路
昨天由於mood not good,所以缺勤一天,今天滿血復活,開始更新!
在面試中常見的常見的排序算法有冒泡排序、選擇排序、插入排序、歸併排序、隨機快排、堆排序和希爾排序這七種方式!雖然冒泡排序和選擇排序基本上已經沒有人使用了,但這種教科書式的思維還是值得學習的!我們接下來就來談談這幾種排序算法的優劣!
冒泡排序
冒泡排序的思路十分的清楚,就是一個數列從左到右依次兩兩比對,如果左邊的數大於右邊的數則交換兩者的位置。否則保持不變,這樣的話,每一次走完一趟都可以確定一個元素的位置,並且需要n-1趟!因此外循環次數爲N-1,內循環則從N-1依次遞減!因爲沒走完一趟,確定位置的元素個數都會加一!
// 一:冒泡排序算法
void bubbleSort(vector<int> &list) {
if (list.size() < 2) {
return;
}
for (int end = list.size() - 1; end > 0; end--) {
for (int i = 0; i < end; i++) {
if (list[i] > list[i + 1]) {
swap(list[i], list[i + 1]);
}
}
}
}
選擇排序
選擇排序的思路是從一個數列中選擇一個值出來,然後計算剩餘數的最小值,如果當前數大於剩餘數的最小值,那麼我們就交換這兩個數的位置。注意每次我們選擇一個數後,剩餘數都會減一,相應的確定位置的元素數也會加一。並且判斷最小值儘量不要使用庫函數,特別面試的時候,這樣會很low,由於進出函數的壓棧出棧會影響算法的性能!
// 二:選擇排序算法
void SelectSort(vector<int> &list) {
if (list.size() < 2) {
return;
}
for (int i = 0; i < list.size(); i++) {
int min = i;
for (int j = i + 1; j < list.size(); j++) {
min = list[j] < list[min] ? j : min; // 選擇排序中的最小值不可以使用庫函數
}
swap(list[min], list[i]);
}
}
插入排序
插入排序的核心思路是,對整個數列進行遍歷,將當前節點i有序的插入到一個有序列表[0, i-1]中去,爲什麼列表[0, i-1]是一個有序的列表呢?很簡單,由於剛開始時那個列表只有一個數,必有序。如何有序插入一個節點呢?回憶一下冒泡排序,每走一趟是不是可以確定一個數的有序位置!因此,和冒泡排序類似,**這個有序插入一個節點的方法也就是冒泡排序走一趟的方法!**從而整個數列遍歷後,他們都有序了!
// 三:插入排序算法
void InsertSort(vector<int> &list) {
if (list.size() < 2) {
return;
}
for (int i = 1; i < list.size(); i++) {
for (int j = i - 1; j >= 0 && list[j + 1] < list[j]; j--) {
swap(list[j], list[j + 1]);
}
}
}
歸併排序
首先我們來看合併的函數,也就是merge函數,這個函數只是做合併的算法,只能用於兩個有序列表之間,我們就假設一個list分成兩部分,L到mid,mid到R,這兩部分均爲有序數組,我們進行合併!首先準備一個輔助數組用於儲存合併後的整個列表,接着這兩個部分進行比較,誰小則將誰放入輔助數組,如果一個部分放完了,則將另一部分直接放入輔助數組,不再進行比較了!
怎麼來使兩個部分有序呢?這就用到了一個分而治之的思想,使用遞歸的方法對數組進行分解,直到將這兩個部分分解成每個每個部分只包含一個節點,其必定有序!接着就一一合併就好啦!!遞歸可以很好的實現我們這個思路!
// for MergeSort
void merge(vector<int>& list, int L, int mid, int R) {
vector<int> help(R - L + 1);
int i = 0, p1 = L, p2 = mid + 1;
while (p1 <= mid && p2 <= R) {
help[i++] = list[p1] < list[p2] ? list[p1++] : list[p2++];
}
while (p1 <= mid) {
help[i++] = list[p1++];
}
while (p2 <= R) {
help[i++] = list[p2++];
}
for (i = 0; i < help.size(); i++) {
list[L + i] = help[i];
}
}
// for MergeSort
void sortProcess(vector<int>& list, int L, int R) {
if (L == R) {
return;
}
int mid = L + (R-L) / 2; // 相比 (R+L)/ 2 可以防止數據溢出
sortProcess(list, L, mid);
sortProcess(list, mid + 1, R);
merge(list, L, mid, R);
}
// 四:歸併排序算法(O(N*logN)) master公式計算遞歸複雜度,條件時同等規模
void MergeSort(vector<int> &list) {
if (list.size() < 2) {
return;
}
sortProcess(list, 0, list.size() - 1);
}
複雜度分析
由上表可得,前三種簡單排序的時間複雜度平均爲O(n^2),而歸併排序的時間複雜度爲O(N*logN),由於歸併排序牽涉到了遞歸算法,對於遞歸算法其時間複雜度分析可以使用Master公式來說明!這個Master公式大家自行百度瞭解!
完整測試文件(C++版),文件名爲:常見排序算法(重點),請關注我的個人公衆號 (算法工程師之路),回覆"左神算法基礎CPP"即可獲得,並實時更新!希望大家多多支持哦~
公衆號簡介:分享算法工程師必備技能,談談那些有深度有意思的算法,主要範圍:C++數據結構與算法/深度學習(CV),立志成爲Offer收割機!堅持分享算法題目和解題思路(Day By Day)