各種排序演算法詳解C++實現
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.選擇排序
時間複雜度為
,空間複雜度
。
第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.插入排序
時間複雜度
,空間複雜度
。
通過掃描前面已排序的子列表,將位置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.歸併排序
時間複雜度
,空間複雜度為
。
先將所有數兩個一組排序,再將兩組四個數一起排序為新的一組,迴圈下去每次合併兩組,直到所有數有序。
遞迴法:
#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.快速排序
時間複雜度
,空間複雜度
。
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.堆排序
時間複雜度
,空間複雜度
。
每次講大根堆的堆頂元素從後往前排列,然後將最後元素放到堆頂,對堆重新序列化,然後再取出堆頂元素。
#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.希爾排序,空間複雜度
。
時間複雜度
,時間複雜度依賴於步長的選擇。
#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),空間複雜度為
,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.
:氣泡排序,選擇排序,插入排序
2.
:歸併排序,快速排序,希爾排序,堆排序
3.
:計數排序,基數排序
空間複雜度
1.
:氣泡排序,選擇排序,插入排序,希爾排序,堆排序
2.
:快速排序
3.
:歸併排序
4.
:桶排序
排序演算法的穩定性
穩定性:相同元素,元素順序在排序前與排序後保持不變。
不穩定排序:選擇排序,快速排序,希爾排序,堆排序。
穩定排序:氣泡排序,插入排序,歸併排序,計數排序,基數排序,桶排序。
工程應用
工程上的排序是綜合排序
陣列較小時,是插入排序
陣列較大時,快速排序或者其他
的排序。
題目一
已知一個幾乎有序的陣列,幾乎有序是指,如果把陣列排好序的話,每個元素移動的距離不超過k,並且k相對於陣列長度來說很小。請問選擇什麼方法對其排序比較好。
分析:
時間複雜度為
的計數與基數排序,因為不知道是否連續,不考慮。
時間複雜度為
,氣泡排序與選擇排序的複雜度與原始順序無關,插入排序可以考慮時間複雜度為
。
時間複雜度為
,歸併排序與快速排序的複雜度與原始順序無關。
答案:改進後的堆排序。每次建立k個元素的小根堆,然後取堆頂元素。時間複雜度為
。
題目二
判斷陣列中是否有重複值,必須保證額外空間複雜度為
.
分析: 若沒有空間複雜度限制,使用雜湊表。
先排序,後判斷。
希爾排序,或堆排序。
題目三
把兩個有序數組合併為一個數組,第一個陣列空間正好可以容納兩個陣列的元素。
分析:從後往前比較,可以避免第一個陣列的元素往後平移。
題目四
荷蘭國旗問題。只包含0,1,2的整數陣列進行排序,要求使用交換,原地排序,而不是利用計數進行排序。
分析:調整過程與快排劃分過程類似,時間複雜度為
,額外空間複雜度為
。
遍歷陣列之前,在陣列兩邊設立0區,與2區,每次將0,2分別放入各自對應區域。噹噹前位置與2區位置重合時,停止。
題目五
在行與列都有序的二維陣列中找數。
分析:時間複雜度為
,M行,N列,空間複雜度為
。
從左下角或者右上角開始找。
題目六
找出需要排序的最短子陣列長度。例如[1,5,4,3,2,6,7],返回4,因為只有[5,4,3,2]需要排序。
分析:最優解時間複雜度為
,額外空間複雜度為
。
首先從左到右遍歷陣列,用一個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。
分析:最優解時間複雜度為
,空間複雜度為
。首先遍歷一遍陣列,選出最大值與最小值,然後將最大最小之間劃分為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;
}