排序算法比較
冒泡排序 | 選擇排序 | 插入排序 | 希爾排序 | 歸併排序 | 堆排序 | 快速排序 | |
---|---|---|---|---|---|---|---|
時間(平均) | O(n2) | O(n2) | O(n2) | O(n1.3) | O(nlogn) | O(nlogn) | O(nlogn) |
時間(最好) | O(n) | O(n2) | O(n) | O(n) | O(nlogn) | O(nlogn) | O(nlogn) |
時間(最差) | O(n2) | O(n2) | O(n2) | O(n2) | O(nlogn) | O(nlogn) | O(n2) |
空間 | O(1) | O(1) | O(1) | O(1) | O(n) | O(1) | O(nlogn) |
穩定性 | 穩定 | 不穩定 | 穩定 | 不穩定 | 穩定 | 不穩定 | 不穩定 |
穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面。
時間複雜度:對排序數據的總的操作次數。反映當n變化時,操作次數呈現的規律。
空間複雜度:指算法在計算機內執行時所需存儲空間的度量,是數據規模n的函數。
七種排序算法
-
冒泡排序
冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果它們的順序錯誤就把它們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因爲越小的元素會經由交換慢慢“浮”到數列的頂端。
-
算法描述
- 比較相鄰元素,如果第一個比第二個大,就交換他們兩個;
- 對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對,這樣最後的元素就是最大元素;
- 針對所有元素重複以上步驟,除了最後一個;
- 重複步驟1~3,直到排序完成。
-
代碼實現
void swap(int &a, int &b) { int temp = a; a = b; b = temp; } vector<int> bubbleSort(vector<int> vec) { int len = vec.size(); for(int i = 0; i < len-1; ++i) { for(int j = 0; j < len - i - 1; ++j) { if(vec[j] > vec[j + 1]) swap(vec[j],vec[j + 1]); } } return vec; }
-
-
選擇排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放在已排序序列的末尾。以此類推,直到所有元素均排序完畢。
-
算法描述
n個記錄的直接選擇排序可經過n-1趟直接選擇排序得到有序結果,具體算法如下:
- 初始狀態:無序區爲R[1…n],有序區爲空;
- 第一趟排序(i=1,2,3,…,n-1)開始時,當前有序區和無序區分別爲R[1,…,i-1]和R(i…n)。該趟排序從當前無序區中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R交換,使R[1…i]和R[i+1…n)分別變爲記錄個數增加1個的新的有序區和記錄個數減少1個的新的無序區;
- n-1趟結束,數組有序化了。
-
代碼實現
vector<int> selectionSort(vector<int> vec) { int len = vec.size(); int minIndex; for(int i = 0; i < len - 1; ++i) { minIndex = i; for(int j = i + 1; j < len; ++j) { if(vec[j] < vec[minIndex]) //尋找最小值 minIndex = j; //將最小值的索引保存 } swap(vec[i], vec[min]); //交換最小值與當前無序集的第一個元素 } return vec; }
-
算法分析
選擇排序無論怎麼樣的數據,時間複雜度都是O(n2),所以使用選擇排序時,數據規模越小越好。
-
-
插入排序
通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。
-
算法描述
- 從第一個元素開始,該元素可以認爲已經被排序;
- 取出下一個元素,在已經排序的元素序列中從後向前掃描;
- 如果該元素(已排序)大於新元素,將該元素移到下一個位置;
- 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
- 將新元素插入到該位置後
- 重複步驟2~5。
-
代碼實現
vector<int> insertionSort(vector<int> vec) { int len = vec.size(); int preIndex, cuurent; for(int i = 1; i < len; ++i) { preIndex = i - 1; //前一個下標,用於比較 current = vec[i]; //存當前未排序的元素,因爲後續移動過程會佔用vec[i],因此現將其保存起來 while(preIndex >= 0 && vec[preIndex] > current) //只有當前一個元素下標>=0,並且前一個元素大於當前元素時,纔會進行移動 { vec[preIndex + 1] = vec[preIndex]; } vec[preIndex + 1] = current; //否則,直接放在前一個元素後第一個位置 } return vec; }
-
算法分析
在一個數組中進行,只需要O(1)的空間複雜度。插入排序是一種穩定排序算法。
-
-
希爾排序
希爾排序是第一個突破O(n2)的排序算法,是簡單插入排序的改進版。它會優先比較距離較遠的元素。希爾排序又叫縮小增量排序。
-
算法描述
先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,具體算法如下:
- 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk = 1;
- 按增量序列個數k,對序列進行k趟排序;
- 每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m的子序列,分別對個子表進行直接插入排序。僅增量因子爲1時,整個序列作爲一個表來處理,表長即爲整個序列長度。
-
代碼實現
vector<int> shellSort(vector<int> vec) { int len = vec.size(); int temp; int gap = 1; while(gap < len / 3) { gap = gap * 3 + 1; } for(gap; gap > 0; gap = floor(gap / 3)) { for(int i = gap; i < len; i++) { temp = vec[i]; for(int j = i - gap; j > 0 && vec[j] >temp; j -= gap) { vec[j + gap] = vec[j]; } vec[j + gap] = temp; } } return vec; }
-
-
歸併排序
歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法的一個典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。
-
算法描述
- 把長度爲n的輸入序列分成兩個長度爲n/2的子序列;
- 對這兩個子序列分別採用歸併排序;
- 將兩個排序好的子序列合併成一個最終的排序序列。
-
代碼實現
void Merging(int *list1, int list1_size, int *list2, int list2_size) { int i = 0, j = 0, k = 0, m = 0; int temp[MAXSIZE]; //臨時數組,用於存放排好序的序列 while(i < list1_size && j < list2_size) //對兩組元素進行排序 { if(list1[i] < list2[j]) { temp[k] = list1[i]; k++; i++; } else { temp[k] = list2[j]; k++; j++; } while(i < list1_size) //當list2沒有元素時,將list1的元素依次放入尾部 { temp[k] = list1[i]; i++; } while(j < list2_size) { temp[k] = list2[j]; j++; } for(m = 0; m < (list1_size + list2_size); m++) //將排好序的元素放在list1中 { list1[m] = temp[m]; } } } void MergeSort(int k[], int n) { while(n > 1) { int *list1 = k; //前半部分的起始指針 int list1_size = n / 2; //前半部分的長度 int *list2 = k + n / 2; //後半部分的起始指針 int list2_size = n - list1_size; //後半部分的長度 //開始遞歸 MergeSort(list1, list1_size); MergeSort(list2, list2_size); Merging(list1, list1_size, list2, list2_size); } }
-
算法分析
歸併排序是一種穩定的排序方法。和選擇排序一樣,歸併排序的性能不受輸入數據的影響,但是表現比選擇排序好,代價是需要額外的空間。
-
-
快速排序
通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
-
算法描述
快速排序使用分治法來把一個串分爲兩個子串。具體算法如下:
- 從數列中挑出一個元素,成爲“基準”;
- 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準後面。在這個分區退出之後,該基準就處於數列的中間位置。這個稱爲分區操作;
- 遞歸地把小於基準元素的子數列和大於基準元素的子數列排序。
-
代碼實現
int Partition(vector<int> &vec, int low, int high); //分區操作 void QSort(vector<int> &vec, int low, int high); void QuickSort(vector<int> &vec) { QSort(vec, 0, vec.size()); } void QSort(vector<int> &vec, int low, int high) { int pivot; if(low < high) { pivot = Patition(vec, low, high); QSort(vec, low, pivot - 1); QSort(vec, pivot + 1, high); } } int Partition(vector<int> &vec, int low, int high) { int key; key = vec[low]; //選擇第一個元素爲基準 while(low < high) { while(low < high && vec[high] >= key) high--; swap(vec, low, high); while(low < high && vec[low] <= key) low++; swap(vec, low, high); } renturn low; }
-
-
堆排序
利用堆這種數據結構所設計的一種排序算法。堆既是一種近似完全二叉樹結構,同時滿足一定的性質,即子結點的鍵值或索引總是小於或大於它的父節點。
-
算法描述
- 將初始待排序關鍵字序列(R1,R2,…,Rn)構建成大頂堆,此堆爲初始的無序區;
- 將堆頂元素R[1]與最後一個元素R[n]交換,此時得到新的無序區(R1,R2,…,Rn-1)和新的有序區(Rn),且滿足R[1,2…n-1] <= R[n];
- 由於交換後新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,…Rn-1)調整爲新堆,然後再次將R[1]與無序區最後一個元素交換,得到新的無序區。不斷重複此過程,直到有序區的元素個數爲n-1,則整個排序過程完成。
-
代碼實現
void HeapAdjust(vector<int> &vec, int s, int n) { int i; int temp = vec[s]; //根 for(i = 2 * s; i <= n; i*=2) //從該節點的左孩子開始比較 { if(i < n && vec[i] < vec[i+1]) //如果左孩子小於右孩子 i++; if(temp >= vec[i]) break; vec[s] = vec[i]; s = i; } vec[s] = temp; } void HeapSort(vector<int> &vec) { int i; int n = vec.size(); for(i = n / 2; i >= 0; i--) { HeapAdjust(vec, i, n); //調整爲大頂堆 } for(i = n; i >=1; i--) { swap(vec, 0, i); //交換第一個元素和當前最後一個元素 HeapAdjust(vec, 0, i-1); //繼續調整 } }
-