面試中的排序算法(Part 1)

文章和資源同步更新至微信公衆號:算法工程師之路

昨天由於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)
算法工程師之路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章