目錄
1.歸并排序
1.1 遞歸實現歸并排序:
?1.2 非遞歸實現歸并排序
1.3 歸并排序的特性總結:
1.4 外部排序
2.計數排序
2.1 操作步驟:
2.2 計數排序的特性總結:
3. 7種常見比較排序比較
1.歸并排序
基本思想:
歸并排序(MERGE-SORT)是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide andConquer)的一個非常典型的應用。將已有序的子序列合并,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合并成一個有序表,稱為二路歸并。
歸并排序核心步驟:
?動圖演示:
1.1 遞歸實現歸并排序:
歸并排序類似于二叉樹中的后序遍歷,先讓整個數組分為兩個子序列,歸并這兩部份子序列,但是歸并需要兩部份子序列有序,然后取小的尾插到一個新開辟的數組中,歸并完成后后再拷貝回原數組,如何讓子序列有序,還要再次將每個子序列分為兩部分,直到每個子序列只有一個值,這時已經遞歸到最深處,然會遞歸向回歸并。
遞歸代碼實現:
//歸并排序
//開辟好空間后由下面元素調用此函數
void _MergeSort(int* arr, int* tmp, int begin, int end)
{if (begin == end){return;}int midi = (begin + end) / 2;_MergeSort(arr, tmp, begin, midi);_MergeSort(arr, tmp, midi+1, end);int begin1 = begin;int end1 = midi;int begin2 = midi + 1;int end2 = end;int i = begin;//歸并 取小的尾插到開辟的空間while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[i++] = arr[begin1++];}else{tmp[i++] = arr[begin2++];}}while (begin1 <= end1){tmp[i++] = arr[begin1++];}while (begin2 <= end2){tmp[i++] = arr[begin2++];}//將歸并好的兩組數據拷貝會原數組memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));}void MergeSort(int* arr, int n)
{//開辟空間int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(arr, tmp, 0, n - 1);
}
小區間優化
//小區間優化
if (end - begin +1<10)
{//使用插入排序InsertSort(arr + begin, end - begin + 1);return;
}
優化的本質是減小遞歸調用的次數,由于二叉樹的性質。我們可以得出滿二叉樹后三層大約占總個數的85%。為了減小遞歸開銷,我們可以將小區間的遞歸調用改為直接插入排序,可以提高一點排序的性能,但也不會提高很多。快排也可以使用這種方式優化。
?1.2 非遞歸實現歸并排序
我們可以先讓每組gap=1個數據,每次歸并兩組,然后在讓gap*=2,再次歸并,直到gap>n。
代碼實現:
//非遞歸實現歸并排序
void MergeSortNonR1(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);//每組有gap個數據,歸并兩組int gap = 1;while (gap < n){int j = 0;for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;if (end1 >= n || begin2 >= n)//不需要歸并{break;}//修正if (end2 >= n){end2 = n - 1;}//歸并while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[j++] = arr[begin1++];}else{tmp[j++] = arr[begin2++];}}while (begin1 <= end1){tmp[j++] = arr[begin1++];}while (begin2 <= end2){tmp[j++] = arr[begin2++];}//將歸并后的兩組數據 拷貝回原數組 memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}
}
邊界越界問題:
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
begin1不會越界,因為begin1 = i,i 復合循環條件?。
- end1,begin2,end2都越界
- begin2,end2越界
- end2越界
1.?end1,begin2,end2都越界
???此時不需要歸并直接跳出循環。
2.?begin2,end2越界
?此時也不需要歸并直接跳出循環。
3.?end2越界
此時需要歸并,但是我們要修改end2,將end2改為n-1。
代碼:
if (end1 >= n || begin2 >= n)//不需要歸并{break;}//修正if (end2 >= n){end2 = n - 1;}
1.3 歸并排序的特性總結:
- 歸并的缺點在于需要O(N)的空間復雜度,歸并排序的思考更多的是解決在磁盤中的外排序問題。
- 時間復雜度:O(N*logN)
- 空間復雜度:O(N)
- 穩定性:穩定
1.4 外部排序
概念:當數據元素太多不能同時放在內存中,根據排序過程的要求不能在內外存之間移動數據的排序。
在我們所學的排序算法中,只有非遞歸歸并排序的思想可以用于外部排序。其他排序算法都只適用于內部排序,因為他們都使用了下標來進行隨機存取,而非遞歸歸并排序不需要,是順序存取,這里舉個例子:
假如我們由100億個整數要排序,也就是大約40G,而我們的內存中只有1G,步驟:
- 把40G的文件分為40份。
- 讓每份文件依次放到內部中排序,讓40份文件內部有序。
- 兩兩歸并,分別從兩個文件中讀一個數據,然后選小的寫文件,這時就與非遞歸歸并排序相同了。
2.計數排序
思想:計數排序又稱為鴿巢原理,是一種非比較排序,是對哈希直接定址法的變形應用。
2.1 操作步驟:
- 統計相同元素出現次數
- 根據統計的結果將序列回收到原來的序列中
?代碼實現:
// 計數排序
void CountSort(int* arr, int n)
{//遍歷 確定最大值與最小值int max = arr[0];int min = arr[0];for (int i = 0; i < n; i++){if (arr[i] < min){min = arr[i];}if (arr[i] > max){max = arr[i];}}//遍歷計數int range = max - min + 1;int* CountA = (int*)malloc(sizeof(int) * range);memset(CountA, 0, sizeof(int) * range);for (int i = 0; i < n; i++){CountA[arr[i] - min]++;}//回收到原數組int j = 0;for (int i = 0; i < range; i++){while (CountA[i]--){arr[j++] = i + min;}}
}
2.2 計數排序的特性總結:
- 計數排序在數據范圍集中時,效率很高,但是適用范圍及場景有限。
- 時間復雜度:O(MAX(N,范圍))
- 空間復雜度:O(范圍)
- 穩定性:穩定
3. 7種常見比較排序比較
排序方法 | 平均情況 | 最好情況 | 最壞情況 | 輔助空間 | 穩定性 |
---|---|---|---|---|---|
冒泡排序 | O(N^2) | O(N) | O(N^2) | O(1) | 穩定 |
簡單選擇排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 不穩定 |
直接插入排序 | O(N^2) | O(N) | O(N^2) | O(1) | 穩定 |
希爾排序 | O(NlogN)~O(N^2) | O(N^1.3) | O(N^2) | O(1) | 不穩定 |
堆排序 | O(NlogN) | O(NlogN) | O(N*logN) | O(1) | 不穩定 |
歸并排序 | O(NlogN) | O(NlogN) | O(N*logN) | O(n) | 穩定 |
快速排序 | O(NlogN) | O(NlogN) | O(N^2) | O(logn)~O(n) | 不穩定 |
本篇結束!