常用排序算法 C++實現

待更新

算法 平均時間複雜度 最壞時間複雜度 空間複雜度 是否穩定排序 是否可以用於鏈式結構 適用情況
快速排序 O(nlog2n) 數組有序時O(n^2) O(log2n)~O(n) 很難用於鏈式 n較大,且數組無序
歸併排序 O(nlog2n) O(nlog2n) O(n+log2n) 歸併排序比較佔內存,但是排序效率高,適用於n較小的情況
簡單排序 O(n^2) O(n^2) O(1) 最樸實的排序,效率非常低
冒泡排序 O(n^2) O(n^2) O(1) 改進的冒泡排序最好的情況爲O(n)
簡單選擇排序 O(n^2) O(n^2) O(1) 簡單選擇排序比冒泡排序要快,在元素本身佔較大空間時,比直接插入快
堆排序 O(nlogn) O(nlogn) O(1) 堆排序是簡單選擇的改進,不適合待排序個數較少的情況
直接插入排序 O(n^2) O(n^2) O(1) 直接插入比簡單選擇排序要快,如果元素基本有序,可以使用插入排序
折半插入排序 O(n^2) O(n^2) O(1) 查找部分由原來的順序查找,變成了折半查找,所以不管待排序的數是否比有序序列中的最後一個元素的值大還是小,都要從中間開始比較,所以其適用於序列初始無序的情況
希爾排序 O(nlogn~O(n^2)) O(n^2) O(1) 最好情況爲O(n^1.3),性能取決於增量的大小,是插入排序的改進
基數排序 O(d(n+rd)) O(d(n+rd)) O(n+rd) ·

下面的圖片轉載自牛客網,侵刪
在這裏插入圖片描述

1.快速排序

快速排序的思想在於通過一次交換來解決多個元素逆序的問題,要解決多個元素逆序問題就不能和冒泡一樣只交換相鄰元素的位置,所以快速排序會跨着跳。

不同於使用兩個low和high兩個下標來回對比樞軸交換位置,劍指offer上面的方法形式上更簡單

它的思想在於,把樞軸放在最後一個元素,從左往右開始比較樞軸,如果元素比樞軸小,則將元素移動到左側,所以需要一個small_index來記錄小於樞軸的元素的數量。

如果vec[i]<vec[end]且small_index!=i,則交換vec[i]和vec[samll_index]的位置。這樣一來small_index位置左側的元素,必定小於vec[end].

在遍歷完元素之後,樞軸的位置還在end位置,而small_index之前的元素都是小於樞軸的,所以交換vec[small_index]和vec[end]就可以完成一趟快速排序。

後續只需要遞歸完成快速排序就可以。

int get_Parition(vector<int>& vec,int start,int end) {
	//std::rand();
	int small_index = start;
	for (int i = start; i <=end;++i) {
		if (vec[i]<vec[end])
		{
			if(small_index!=i){
				std::swap(vec[i], vec[small_index]);
			}
			++small_index;
		}
	}
	std::swap(vec[small_index], vec[end]);
	return small_index;
}

void my_quick_sort(vector<int>& vec,int start ,int end) {
	if (start<end) {
		int parition = get_Parition(vec, start, end);
		my_quick_sort(vec, start, parition - 1);
		my_quick_sort(vec, parition + 1, end);


	}
}

//修改版
void q_sort_core(vector<int>& vec,int begin,int end) {
	int v;
	while (begin < end) {
		v = get_pivo(vec, begin, end);
		//if (begin<v-1) {
			q_sort_core(vec, begin, v - 1);
		//}
		begin = v + 1;
	}
}

書上說是尾遞歸優化,但是看起來並不是的,尾遞歸的優化要在return那裏

這裏可以減少遞歸次數的原因在於,這裏在進行後半段排序的時候,檢測了一遍
begin<end所以可以減少遞歸次數。

2.歸併排序

歸併排序利用了完全二叉樹的結構思想,只要進行log2n次歸併就可以完成排序

它的思想在於將數組分爲兩個子數組,將兩個子數組排序後合併爲一個數組,使得這個數組變得有序。

因爲他會兩兩比較元素的大小所以歸併排序是穩定的排序,但是歸併排序每次合併的時候都需要一個額外的空間來保存合併後的數組,這個額外的空間等於數據中元素的大小,所以歸併需要佔用比較多的內存,當數據量巨大的時候就不太適用了。

歸併排序的核心思想:
1.將當前數組拆分爲兩個子數組
2.將兩個子數組排序
3.將兩個子數組歸併爲一個數組

//歸併排序
void merge_sort_core(vector<int>& vec, int begin, int end);
void MergeSort(vector<int>& vec) {
	if (vec.size()==0) {
		return;
	}
	merge_sort_core(vec, 0, vec.size() - 1);
}
void merge_sort_core(vector<int>& vec,int begin,int end) {
	//如果拆分到只剩下一個值,則返回
	if (begin==end) {
		return;
	}
	//將當前的數組,拆分爲兩個部分
	int mid = (begin + end) / 2;
	merge_sort_core(vec, begin, mid);
	merge_sort_core(vec, mid + 1, end);
	//上面的代碼表示將兩個子數組已經合併好了,下面把上面的兩個子數組合並
	//由於兩個子數字是有序的,將兩個有序的合併後到一起需要一段輔助空間
	vector<int> copy_vec(end-begin+1);
	//將兩個子數組中的值傳入到輔助數組中
	int arr_1_index = begin;
	int arr_2_index = mid + 1;
	int copy_index = 0;
	while (arr_1_index<=mid&&arr_2_index<=end) {
		if (vec[arr_1_index]<=vec[arr_2_index]) {
			copy_vec[copy_index] = vec[arr_1_index];
			++arr_1_index;
		}
		else {
			copy_vec[copy_index] = vec[arr_2_index];
			++arr_2_index;
		}
		++copy_index;
	}
	//將剩餘的部分都加入
	while (arr_1_index<=mid) {
		copy_vec[copy_index] = vec[arr_1_index];
		++arr_1_index;
		++copy_index;
	}
	while (arr_2_index <= end) {
		copy_vec[copy_index] = vec[arr_2_index];
		++arr_2_index;
		++copy_index;
	}
	//將排序好的部份拷貝給原來的數組
	std::copy(copy_vec.begin(), copy_vec.end(), vec.begin() + begin);
}

3.簡單排序

/*
最簡單的排序,但是不是冒泡
每一個數字j都和當前的i進行對比
如果當前的i大於j則交換二者
所以一輪次交換之後,可以得到一個最小的值

時間複雜度爲O(n^2)
*/
void simple_sort(vector<int>& vec) {
	for (int i = 0; i < vec.size();++i) {
		for (int j = i + 1; j < vec.size();++j) {
			if (vec[i]>vec[j]) {
				swap(vec[i],vec[j]);
			}
		}
	}
	cout << endl;
}

4. 冒泡排序

/*
冒泡排序
冒泡顧名思義是從下往上冒,所以比較元素的時候是,從下往上的開始比較的

如果一個算法,已經是有序的
1,2,3,4,5,6,7,8,9
排序還是會一直往下走,從9到1,從9-2。
因爲序列已經是有序的了,所以再次比較其實就是浪費性能,所以可以改進

時間複雜度爲O(n^2)
*/
void bubble_sort(vector<int>& vec) {
	for (int i = 0; i < vec.size();++i) {
		bool is_sort = true;
		for (int j = vec.size()-2; j >= i;--j) {
			if (vec[j]>vec[j+1]) {
				swap(vec[j],vec[j+1]);
				is_sort = false;
			}
		}
		if (is_sort) {
			return;
		}
	}
}

5.簡單選擇排序

/*

簡單選擇排序的思想是,從一個無序的序列中,選擇一個最小的值

插入到有序序列的最後面

時間複雜度爲O(n^2)
*/
void simple_select_sort(vector<int>& vec) {
	for (int i = 0; i < vec.size();++i) {
		int min_index = i;
		for (int j = i+1; j < vec.size();++j) {
			if (vec[min_index]>vec[j]) {
				min_index = j;
			}
		}
		if (min_index!=i) {
			swap(vec[min_index],vec[i]);
		}
	}
}

6. 直接插入排序

/*
插入排序的思想是從選擇無序序列中的第一個元素,插入到有序序列中

在尋找插入位置的時候,可以使用二分查找

*/
void insert_sort(vector<int>& vec) {
	for (int i = 0; i < vec.size()-1;++i) {
		if (vec[i]>vec[i+1]) {
			//保存當前的值
			int value = vec[i + 1];
			//前面的值往後覆蓋
			int j = i+1;
			//使用j>0的方法,可以保證j-1不會越界
			for (; j > 0&&value < vec[j-1];--j) {
				vec[j] = vec[j-1];
			}
			//找到這個位置,然後插進去
			vec[j] = value;
		}
	}
}

7. 折半插入排序

void binary_insert_sort(vector<int>& vec) {
	for (int i = 1; i < vec.size();++i) {
		//利用折半查找
		int begin = 0;
		int end = i;
		int mid ;
		//尋找需要插入的點,mid
		while (begin<=end) {
			mid = begin + (end - begin) / 2;
			//cout << vec[mid] << endl;
			if (vec[i]>vec[mid]) {
				begin = mid + 1;
			}
			else {
				end = mid - 1;
			}
		}
		int value = vec[i];

		int j = i;
		//把從mid開始的點都移動到後面去
		for (;j>mid ;--j) {
			vec[j] = vec[j-1];
		}
		//mid位置的值,賦值給當前的value
		vec[mid] = value;
	}
}

8. 堆排序

如果是升序排列,則構造大根堆。
如果是降序排列,則構造小根堆。
這裏是構造大根堆。

//
//完全二叉樹根節點和左右子樹的關係,需要從1開始計算,但是數組的下標是從0開始的。
void heap_adjust(vector<int>& vec,int index,int length) {
	index += 1;
	int i = index * 2;
	int temp = vec[index - 1];
	for (; i <= length;i*=2) {
		//選擇左右子樹中,較大值的節點
		if (i<length&&vec[i]>vec[i-1]) {
			i += 1;
		}
		if (temp<vec[i-1]) {
			vec[i / 2 - 1] = vec[i - 1];
		}
		else {
			break;
		}
	}
	vec[i / 2 - 1] = temp;
}
void heap_sort(vector<int>& vec) {
	//1. 構建堆
	for (int i = vec.size() / 2; i >=0;--i) {
		heap_adjust(vec, i, vec.size());
	}
	//2.堆排序
	for (int i = vec.size() - 1; i > 0;--i) {
		std::swap(vec[0], vec[i]);
		heap_adjust(vec, 0, i - 1);
	}
}

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