1. 程式人生 > >各種排序演算法詳解C++實現

各種排序演算法詳解C++實現

1.氣泡排序
時間複雜度 O ( n 2 ) O(n^2) ,空間複雜度 O

( 1 ) O(1)
a.比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
b.對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
c.針對所有的元素重複以上的步驟,除了最後一個。
d.持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
若為n個數,一共需遍歷n-1次,每次講最大的數放到最後一位。第i次需遍歷前n-i+1個數。

#include<iostream>
#include<thread>
using std::cout;
using std::endl;

void swap(int &a, int &b) {
	a = a + b;
	b = a - b;
	a = a - b;
}

void bubbleSort(int *unsortArray, const int &length) {
	for (int i = 0; i < length; ++i) {
		for (int j = 0; j < length - 1 - i; ++j) {
			if (unsortArray[j] > unsortArray[j + 1]) {
				swap(unsortArray[j], unsortArray[j + 1]);
			}
		}
	}
}

int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	bubbleSort(sort, length);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

2.選擇排序
時間複雜度為 O ( n 2 ) O(n^2) ,空間複雜度 O ( 1 ) O(1)
第1趟,在待排序記錄r[1]r[n]中選出最小的記錄,將它與r[1]交換;第2趟,在待排序記錄r[2]r[n]中選出最小的記錄,將它與r[2]交換;以此類推,第i趟在待排序記錄r[i]~r[n]中選出最小的記錄,將它與r[i]交換,使有序序列不斷增長直到全部排序完畢。

#include<iostream>
#include<thread>
using std::cout;
using std::endl;

void swap(int &a, int &b) {
	int tem = 0;
	tem = a;
	a = b;
	b = tem;
}

void selectionSort(int *unsortArray, const int &length) {
	int minIndex = -1;
	for (int i = 0; i < length; ++i) {
		minIndex = i;
		for (int j = 0; j < length - i; ++j) {
			if (unsortArray[j + i] < unsortArray[minIndex]) {
				minIndex = j + i;
			}
		}
		swap(unsortArray[i], unsortArray[minIndex]);
	}
}

int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	selectionSort(sort, length);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

3.插入排序
時間複雜度 O ( n 2 ) O(n^2) ,空間複雜度 O ( 1 ) O(1)
通過掃描前面已排序的子列表,將位置i處的元素定位到從0到i的子列表之內的正確的位置上。

#include<iostream>
#include<thread>
using std::cout;
using std::endl;

void insertSort(int *unsortArray, const int &length) {
	for (int i = 1; i < length; ++i) {
		for (int j = 0; j < i + 1; ++j) {
			if (unsortArray[j] > unsortArray[i]) {
				int tem = unsortArray[i];
				for (int k = 0; k < i - j; ++k) {
					unsortArray[i - k] = unsortArray[i - k - 1];
				}
				unsortArray[j] = tem;
				break;
			}
		}
	}
}

int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	insertSort(sort, length);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

4.歸併排序
時間複雜度 O ( N l o g N ) O(N*logN) ,空間複雜度為 O ( N ) O(N)
先將所有數兩個一組排序,再將兩組四個數一起排序為新的一組,迴圈下去每次合併兩組,直到所有數有序。
遞迴法:

#include<iostream>
#include<thread>
#include<vector>
using std::cout;
using std::endl;

void merge_sort(int *unsortArray, const int &start, const int &mid, const int &end) {
	std::vector<int> tempArray;
	int i = start;
	int j = mid + 1;
	int k = end - start + 1;
	while (i<=mid && j<=end)
	{
		if (unsortArray[i] <= unsortArray[j]) {
			tempArray.push_back(unsortArray[i++]);
		}
		else{
			tempArray.push_back(unsortArray[j++]);
		}
	}
	while (i<=mid){
		tempArray.push_back(unsortArray[i++]);
	}
	while (j <= end) {
		tempArray.push_back(unsortArray[j++]);
	}
	for (int i = 0; i < k; ++i) {
		unsortArray[start + i] = tempArray[i];
	}
}

void mergeSort(int *unsortArray, const int &start, const int &end) {
	if (start < end)
	{
		int mid = (start + end) / 2;
		mergeSort(unsortArray, start, mid);
		mergeSort(unsortArray, mid + 1, end);
		merge_sort(unsortArray, start, mid, end);
	}
}


int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	mergeSort(sort,0,9);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

非遞迴方法:

#include<iostream>
#include<thread>
#include<vector>
using std::cout;
using std::endl;

void merge(int *unsortArray, const int &start, const int &step, const int &length) {
	const int start2 = start + step;//第二組的起始位置
	int rightLength = -1;//第二組的長度
	int i = 0;
	int j = 0;
	std::vector<int> tempArray;
	if (start2 + step - 1 >= length - 1) {
		rightLength = length - start2;
	}
	else {
		rightLength = step;
	}
	while (i < step && j < rightLength) {
		if (unsortArray[start + i] <= unsortArray[start2 + j]) {
			tempArray.push_back(unsortArray[start + i]);
			++i;
		}
		else {
			tempArray.push_back(unsortArray[start2 + j]);
			++j;
		}
	}
	while (i < step) {
		tempArray.push_back(unsortArray[start + i]);
		++i;
	}
	while (j < rightLength) {
		tempArray.push_back(unsortArray[start2 + j]);
		++j;
	}
	for (i = 0; i < step + rightLength; ++i) {
		unsortArray[start + i] = tempArray[i];
	}

}

void mergeSort(int *unsortArray, const int &length) {//歸併排序,非遞迴方法
	int step = 1;
	while (step < length) {
		for (int i = 0; i <= length - step - 1; i += (step * 2)) {
			merge(unsortArray, i, step, length);
		}
		step *= 2;
	}
}


int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	mergeSort(sort, 10);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

5.快速排序
時間複雜度 O ( N l o g N ) O(N*logN) ,空間複雜度 O ( l o g N )   O ( N ) O(logN)~O(N)
a.先從數列中取出一個數作為基準數。
b.分割槽過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。
c.再對左右區間重複第二步,直到各區間只有一個數。
在每次分割槽的過程中,可以先將選取的比較數放到陣列的最後位置,並設定一個新的區間表示小於比較數的範圍,然後從前到後依次遍歷,若小於比較數則放到新的區間,若大於則不動。
遞迴方法:

#include<iostream>
#include<thread>
#include<vector>
using std::cout;
using std::endl;


void swap(int &a, int &b) {
	int tem = 0;
	tem = a;
	a = b;
	b = tem;
}


void quickSort(int *unsortArray, const int &start, const int &end) {//快速排序,遞迴方法
	if (start < end) {
		swap(unsortArray[start], unsortArray[end]);
		int index = start;
		for (int i = start; i < end; ++i) {
			if (unsortArray[i] < unsortArray[end]) {
				swap(unsortArray[i], unsortArray[index++]);
			}
		}
		swap(unsortArray[index], unsortArray[end]);
		quickSort(unsortArray, start, index - 1);
		quickSort(unsortArray, index + 1, end);
	}
}


int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	quickSort(sort, 0, 9);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

非遞迴方法:

#include<iostream>
#include<thread>
#include<vector>
#include<stack>
using std::cout;
using std::endl;
void swap(int &a, int &b) {
	int tem = 0;
	tem = a;
	a = b;
	b = tem;
}

int quick_sort(int *unsortArray, const int &start, const int &end) {//快速排序劃分區間部分
	int index = 0;
	if (start < end) {
		swap(unsortArray[start], unsortArray[end]);
		index = start;
		for (int i = start; i < end; ++i) {
			if (unsortArray[i] < unsortArray[end]) {
				swap(unsortArray[i], unsortArray[index++]);
			}
		}
		swap(unsortArray[index], unsortArray[end]);
	}
	return index;
}


void quickSort(int *unsortArray, const int &length) {//
	std::stack<int> st;
	st.push(0);
	st.push(length - 1);
	while (!st.empty()) {
		int end = st.top();
		st.pop();
		int start = st.top();
		st.pop();
		int tempIndex = quick_sort(unsortArray, start, end);
		if (start < tempIndex-1) {
			st.push(start);
			st.push(tempIndex - 1);
		}
		if (end > tempIndex+1) {
			st.push(tempIndex + 1);
			st.push(end);
		}
	}	
}


int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	quickSort(sort, 10);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

6.堆排序
時間複雜度 O ( N l o g N ) O(N*logN) ,空間複雜度 O ( 1 ) O(1)
每次講大根堆的堆頂元素從後往前排列,然後將最後元素放到堆頂,對堆重新序列化,然後再取出堆頂元素。

#include<iostream>
#include<thread>

using std::cout;
using std::endl;

template <typename T>void swap(T &a, T &b) {
	T tem =a;
	a = b;
	b = tem;
}


template <typename T> class PQ_ComplHeap {
public:
	T * sortArray;
	int length;
	PQ_ComplHeap(const int &len);
	~PQ_ComplHeap() {
		delete[] sortArray;
		cout<<"我被析構了!!!"<<endl;
	}
	void display();
	void heapify();
	bool inHeap(const int &i,const int &len) {
		return (i > (-1)) && (i < len);
	}
	int lChild(const int &i) {
		return (2 * i + 1);
	}
	int rChild(const int &i) {
		return 2 * (i + 1);
	}
	bool lChildValid(const int &i, const int &len) {//判斷是否有左孩子
		return inHeap(lChild(i), len);
	}
	bool rChildValid(const int &i, const int &len) {  //判斷是否有右孩子
		return inHeap(rChild(i), len);
	}
	int bigger(const int &i, const int &j) {  //判斷哪個節點大
		if (sortArray[i]>=sortArray[j])
		{
			return i;
		}
		else
		{
			return j;
		}
	}
	int percolateDown(const int &aimIndex,const int &len);
	int properParent(const int &father,const int &len);
	void heapSort();

};
template <typename T> int PQ_ComplHeap<T>::properParent(const int &father,const int &len) {  //判斷一個節點以及他的子節點哪個適合做為大根堆頂節點
	if (rChildValid(father,len)) {
		return bigger(father, bigger(lChild(father), rChild(father)));
	}
	else if (lChildValid(father,len)) {
		return bigger(father, lChild(father));
	}
	else
	{
		return father;
	}
}
template <typename T> int PQ_ComplHeap<T>::percolateDown(const int &aimIndex,const int &len) {  //堆元素的下濾
	int temp = aimIndex;
	int j;
	while (temp != (j = properParent(temp,len))) {
		swap(sortArray[temp], sortArray[j]);
		temp = j;
	}
	return temp;
}
template <typename T> void PQ_ComplHeap<T>::heapify() {       //大根堆化
	for (int i = length - 1; inHeap(i,length); --i) {
		percolateDown(i,length);
	}
}

template <typename T> PQ_ComplHeap<T>::PQ_ComplHeap(const int &len) {   //建構函式
	sortArray = new T[length = len];
	for (int i = 0; i < len; ++i) {
		sortArray[i] = i;
	}
}

template <typename T> void PQ_ComplHeap<T>::display() {    //依次列印堆中元素
	for (int i = 0; i < length; ++i) {
		cout << sortArray[i] << endl;
	}
}

template <typename T> void PQ_ComplHeap<T>::heapSort() {  //堆排序
	heapify();
	display();
	cout << "start::" << endl;
	for (int i = 0; i < length; ++i) {
		swap(sortArray[0], sortArray[length - 1 - i]);
		percolateDown(0,length-i-1);
	}
}

int main() {

	PQ_ComplHeap<int> heapSort(10);
	heapSort.heapSort();
	heapSort.display();
	while (1) {
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
	return 1;
}

7.希爾排序,空間複雜度 O ( 1 ) O(1)
時間複雜度 O ( N l o g N ) O(N*logN) ,時間複雜度依賴於步長的選擇。

#include<iostream>
#include<thread>
#include<vector>
#include<stack>
using std::cout;
using std::endl;
void swap(int &a, int &b) {
	int tem = 0;
	tem = a;
	a = b;
	b = tem;
}


void shellSort(int *unsortArray, const int &length, int step) {
	while (step > 0) {
		for (int i = step; i < length; ++i) {
			int tem = i;
			while (tem >= step) {
				if (unsortArray[tem] < unsortArray[tem - step]) {
					swap(unsortArray[tem], unsortArray[tem - step]);
				}
				tem -= step;
			}
		}
		--step;
	}
}

int main() {
	int sort[] = { 9,8,7,6,5,4,3,2,1,0 };
	int length = sizeof(sort) / sizeof(int);
	shellSort(sort, 10, 1);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}
	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

8.基數排序
時間複雜度為O(N),空間複雜度為 O ( M ) O(M) ,M為桶的數量。
不是基於比較的排序,基於桶排序的思想。先將個位桶排序再依次倒出,然後十位。。。

#include<iostream>
#include<thread>
#include<vector>
#include<stack>
#include<queue>
using std::cout;
using std::endl;



int pickNumber(const int &number, const int &loc) {  //返回一個十進位制數的第幾位,個位為0
	int tem = number;
	if (loc > 0) {
		tem = number / (loc * 10);
	}
	return tem % 10;
}

void radixSort(int *unsortArray, const int &length) {  //基數排序
	std::queue<int> bucket[10];
	for (int i = 0; i < 2; ++i) {
		for (int j = 0; j < length; ++j) {
			
			int k = pickNumber(unsortArray[j], i);
			bucket[k].push(unsortArray[j]);
		}
		int k = 0;
		for (int j = 0; j < length; ++j) {
			while (!bucket[j].empty())
			{
				unsortArray[k++] = bucket[j].front();
				bucket[j].pop();
			}
		}
	}
}

int main() {
	int sort[] = { 39,8,27,6,45,4,33,2,41,20 };
	int length = sizeof(sort) / sizeof(int);
	radixSort(sort, 10);

	for (int i = 0; i < length; ++i) {
		cout << sort[i] << endl;
	}

	while (1) {
		std::this_thread::sleep_for(std::chrono::microseconds(1000));
	}
}

排序演算法複雜度總結

時間複雜度

1. O ( N 2 ) O(N^2) :氣泡排序,選擇排序,插入排序
2. O ( N l o g N ) O(NlogN) :歸併排序,快速排序,希爾排序,堆排序
3. O ( N ) O(N) :計數排序,基數排序

空間複雜度

1. O ( 1 ) O(1) :氣泡排序,選擇排序,插入排序,希爾排序,堆排序
2. O ( l o g N ) O ( N ) O(logN)-O(N) :快速排序
3. O ( N ) O(N) :歸併排序
4. O ( M ) O(M) :桶排序

排序演算法的穩定性

穩定性:相同元素,元素順序在排序前與排序後保持不變。
不穩定排序:選擇排序,快速排序,希爾排序,堆排序。
穩定排序:氣泡排序,插入排序,歸併排序,計數排序,基數排序,桶排序。

工程應用

工程上的排序是綜合排序
陣列較小時,是插入排序
陣列較大時,快速排序或者其他 O ( N l o g N ) O(NlogN) 的排序。

題目一

已知一個幾乎有序的陣列,幾乎有序是指,如果把陣列排好序的話,每個元素移動的距離不超過k,並且k相對於陣列長度來說很小。請問選擇什麼方法對其排序比較好。
分析:
時間複雜度為 O ( N ) O(N) 的計數與基數排序,因為不知道是否連續,不考慮。
時間複雜度為 O ( N 2 ) O(N^2) ,氣泡排序與選擇排序的複雜度與原始順序無關,插入排序可以考慮時間複雜度為 O ( N K ) O(N*K)
時間複雜度為 O ( N l o g N ) O(N*logN) ,歸併排序與快速排序的複雜度與原始順序無關。
答案:改進後的堆排序。每次建立k個元素的小根堆,然後取堆頂元素。時間複雜度為 O ( N l o g K ) O(N*logK)

題目二

判斷陣列中是否有重複值,必須保證額外空間複雜度為 O ( 1 ) O(1) .
分析: 若沒有空間複雜度限制,使用雜湊表。
先排序,後判斷。
希爾排序,或堆排序。

題目三

把兩個有序數組合併為一個數組,第一個陣列空間正好可以容納兩個陣列的元素。
分析:從後往前比較,可以避免第一個陣列的元素往後平移。

題目四

荷蘭國旗問題。只包含0,1,2的整數陣列進行排序,要求使用交換,原地排序,而不是利用計數進行排序。
分析:調整過程與快排劃分過程類似,時間複雜度為 O ( N ) O(N) ,額外空間複雜度為 O ( 1 ) O(1)
遍歷陣列之前,在陣列兩邊設立0區,與2區,每次將0,2分別放入各自對應區域。噹噹前位置與2區位置重合時,停止。

題目五

在行與列都有序的二維陣列中找數。
分析:時間複雜度為 O ( M + N ) O(M+N) ,M行,N列,空間複雜度為 O ( 1 ) O(1)
從左下角或者右上角開始找。

題目六

找出需要排序的最短子陣列長度。例如[1,5,4,3,2,6,7],返回4,因為只有[5,4,3,2]需要排序。
分析:最優解時間複雜度為 O ( N ) O(N) ,額外空間複雜度為 O ( 1 ) O(1)
首先從左到右遍歷陣列,用一個max變數記錄遍歷過的最大值,若遍歷過的最大值大於當前數,則為反序,此時只記錄出現反序的最右位置。
然後從右往左遍歷陣列,用一個min變數記錄遍歷過的最小值,若遍歷過的最小值小於當前數,則為反序,此時只記錄出現反序的最左位置。
最右與最左位置中間的範圍(包括最右最左)為需要排序的範圍。

int minSortLength(const int *unsortArray, const int &length) {
	int max = unsortArray[0];
	int rightFlag = 0;
	int min = unsortArray[length - 1];
	int leftFlag = length;
	for (int i = 0; i < length; ++i) {
		if (unsortArray[i] >= max) {
			max = unsortArray[i];
		}
		else{
			rightFlag = i;
		}
	}
	for (int i = length - 1; i >= 0; --i) {
		if (unsortArray[i] <= min) {
			min = unsortArray[i];
		}
		else {
			leftFlag = i;
		}
	}
	return (rightFlag - leftFlag) > 0 ? (rightFlag - leftFlag + 1) : 0;
}

題目七

給定一個整數陣列,返回排序之後,相鄰兩個數的最大差值。例如[1,2,3,7,8],返回4。
分析:最優解時間複雜度為 O ( N ) O(N) ,空間複雜度為 O ( N ) O(N) 。首先遍歷一遍陣列,選出最大值與最小值,然後將最大最小之間劃分為N個桶,代表N個區間,然後將最大值放到第N+1個桶中。然後將所有數放到桶中,比較當前桶的最大值與下一個非空桶的最小值之間的差,選出最大的差值。

int maxDifference(const int *unsortArray, const int &length) {
	int max = unsortArray[0];
	int min = unsortArray[0];
	int len = length + 1;
	std::stack<int> *st = new std::stack<int>[len];
	for (int i = 0; i < length; ++i) {
		if (unsortArray[i] > max) {
			max = unsortArray[i];
		}
		if (unsortArray[i] < min) {
			min = unsortArray[i];
		}
	}
	double intervel = (max - min) / static_cast<double>(length);
	for (int i = 0; i < length; ++i) {
		if (unsortArray[i] < max) {
			int k = (unsortArray[i] - min) / intervel;
			st[k].push(unsortArray[i]);
		}
	}
	int lastMax = min;
	int nowMax = min;
	int nowMin = max;
	int maxLength = 0;
	for (int i = 0; i < len; ++i) {
		nowMin = max;
		lastMax = nowMax;
		while (!st[i].empty()){
			int tem = st[i].top();
			st[i].pop();
			if (tem < nowMin) {
				nowMin = tem;
			}
			if (tem > nowMax) {
				nowMax = tem;
			}
		}
		int temp = nowMin - lastMax;
		if (temp > maxLength) {
			maxLength = temp;
		}
	}
	delete[] st;
	return maxLength;
}