内排序及时间复杂度分析-插入排序选择排序交换排序归并排序分配和索引排序对比...
生活随笔
收集整理的這篇文章主要介紹了
内排序及时间复杂度分析-插入排序选择排序交换排序归并排序分配和索引排序对比...
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
基本概念
什么是排序?
- 排序
- 將序列中的記錄按照排序碼順序排列起來
- 排序碼域的值具有不減(或不增)的順序
- 內排序
- 整個排序過程在內存中完成
- 給定一個序列 R = { r1, r2, ...,rn }
- 其排序碼分別為 k = { k1, k2, ...,kn }
- 排序的目的:將記錄按排序碼重排
- 形成新的有序序列R’={r’1,r’2,...,r’n}
- 相應排序碼為 k’= { k’1, k’2, ...,k’n }
- 排序碼的順序
- 其中 k’1 ≤ k’2 ≤ ... ≤ k’n,稱為不減序
- 或 k’1 ≥ k’2 ≥ ... ≥ k’n ,稱為不增序
排序的穩定性
- 穩定性
- 存在多個具有相同排序碼的記錄
- 排序后這些記錄的相對次序保持不變
- 排序穩定性的意義
- 保持第一關鍵字相同的數據排序前后順序不變,若不穩定的排序要做到這一點,則需要增加第二個關鍵字。
插入排序
直接插入排序算法
template <class Record> void ImprovedInsertSort (Record Array[], int n){ //Array[] 為待排序數組,n 為數組長度 Record TempRecord; // 臨時變量 for (int i=1; i<n; i++){ // 依次插入第 i 個記錄 TempRecord = Array[i]; //從 i 開始往前尋找記錄 i 的正確位置 int j = i-1; //將那些大于等于記錄 i 的記錄后移 while ((j>=0) && (TempRecord < Array[j])){ Array[j+1] = Array[j]; j = j - 1; } //此時 j 后面就是記錄 i 的正確位置,回填 Array[j+1] = TempRecord; } } 復制代碼算法分析
- 穩定
- 空間代價:Θ(1)
- 時間代價:
- 最佳情況:n-1次比較,2(n-1)次移動,Θ(n)
- 最差情況:Θ(n2)
- 比較次數為n(n-1)/2 = O(n2)
- 移動次數為 (n-1)(n-4)/2 = O(n2)
- 平均情況:Θ(n2)
- 性質:
- 序列本身有序的話時間代價為O(N)
- 對于短序列比較有效
Shell排序
算法思想
- 先將序列轉化為若干小序列,然后在小序列里面插入排序
- 逐步增加小序列規模,從而減少小序列個數
- 最后對整個序列掃尾直接插入排序
“增量每次除以2”的Shell排序
template <class Record> void ShellSort(Record Array[], int n) { // Shell排序,Array[]為待排序數組,n為數組長度 int i, delta; // 增量delta每次除以2遞減 for (delta = n/2; delta>0; delta /= 2) for (i = 0; i < delta; i++) // 分別對delta個子序列進行插入排序 //“&”傳 Array[i]的地址,數組總長度為n-i ModInsSort(&Array[i], n-i, delta); // 如果增量序列不能保證最后一個delta間距為1 // 可以安排下面這個掃尾性質的插入排序 // ModInsSort(Array, n, 1); }template <class Record> // 參數delta表示當前的增量 void ModInsSort(Record Array[], int n, int delta) { int i, j; // 對子序列中第i個記錄,尋找合適的插入位置 for (i = delta; i < n; i += delta)// j以dealta為步長向前尋找逆置對進行調整 for(j=i;j>=delta;j-=delta) { if (Array[j] < Array[j-delta]) // 逆置對 swap(Array, j, j-delta);// 交換 else break; }} 復制代碼算法分析
- 不穩定
- 空間代價O(1)
- 時間代價O(n2)
- 選取的增量并不互質,實際上上一輪子序列都排序過了,導致效率過低
- 選取合適的增量序列,可以是時間接近于O(n)
Hibbard 增量序列
- Hibbard 增量序列
- {2k -1,2k-1 -1,...,7,3,1}
- Shell(3) 和 Hibbard 增量序列的 Shell 排序的效率可以達到 Θ(n3/2)
Shell最好的代價
- 呈 2p3q 形式的一系列整數: – 1, 2, 3, 4, 6, 8, 9, 12
- Θ(nlog2n)
選擇排序
- 直接選擇排序
- 堆排序
直接選擇排序
每次從剩下的堆中選擇最小的
template <class Record> void SelectSort(Record Array[], int n) { // 依次選出第i小的記錄,即剩余記錄中最小的那個 for (int i=0; i<n-1; i++) { // 首先假設記錄i就是最小的 int Smallest = i; // 開始向后掃描所有剩余記錄 for (int j=i+1;j<n; j++) // 如果發現更小的記錄,記錄它的位置 if (Array[j] < Array[Smallest]) Smallest = j; // 將第i小的記錄放在數組中第i個位置(從而導致不穩定) swap(Array, i, Smallest); } } 復制代碼算法分析
- 不穩定(因為會交換位置)
- 空間代價O(1)
- 時間代價
- 比較次數O(n2)
- 交換次數n-1
- 總時間代價O(n2)
堆排序
- 選擇類內排序 由于直接選擇排序是從剩余記錄中線性地去查找最大記錄所以效率較低,我們可以采用最大堆來實現,效率會更高
- 選擇類外排序(這一部分后面討論)
- 置換選擇排序
- 贏者樹、敗方樹
算法分析
- 不穩定(Siftdown的過程會有可能導致相同元素出入序列不一致)
- 建堆O(n)
- 刪除堆頂O(log n)
- 總時間代價O(nlog n)
- 空間代價O(1)
- 在只需得到最大或最小的一部分元素時有優勢(部分排序)
交換排序
- 冒泡排序
- 快速排序
冒泡排序
- 算法思想
- 不停地比較相鄰的記錄,如果不滿足排序要求,就 交換相鄰記錄,直到所有的記錄都已經排好序
- 檢查每次冒泡過程中是否發生過交換,如果沒 有,則表明整個數組已經排好序了,排序結束
- 避免不必要的比較
算法分析
- 穩定
- 空間代價O(1)
- 時間代價
- 比較次數
- 最少O(n)
- 最多O(n2)
- 交換次數 平均O(n2)
- 比較次數
- 結論
- 最大平均為O(n2)
- 最小為O(n)
- 冒泡排序和直接選擇排序從時間復雜度上來講是一樣的,都是O(n^2)的排序算法,但是因為冒泡排序是每次交換相鄰兩個,所以在常數上可能要稍微大一點。冒泡排序是穩定的,直接選擇排序是不穩定的。
快速排序
- 思想(分治策略)
- 選擇軸值(pivot)
- 將序列劃分為兩個子序列L和R,使得L中所有記錄都小于或等于軸值,R中記錄都大于軸值
- 對子序列L和R遞歸進行快速排序
- 軸值選擇
- 盡可能使 L, R 長度相等
- 選擇策略:
- 選擇最左邊記錄
- 隨機選擇
- 選擇平均值
時間代價
- 不穩定(軸值的選擇會影響到)
- 最差情況:
- 時間代價:Θ(n2)
- 空間代價:Θ(n)
- 最佳情況:
- 時間代價:Θ(nlog n)
- 空間代價:Θ(log n)
- 平均情況:
- 時間代價:Θ(nlog n)
- 空間代價:Θ(log n)
快速排序優化
- 軸值選擇 RQS
- 小子串不遞歸 (比如閾值 28, 采用直接插入排序等)
- 消除遞歸(用棧,隊列)
- 更多參見這篇文章
- 快速排序比大部分排序算法都要快。盡管我們可以在某些特殊的情況下寫出比快速排序快的算法,但是就通常情況而言,沒有比它更快的了。快速排序是遞歸的,對于內存非常有限的機器來說,它不是一個好的選擇。
歸并排序
歸并排序思想
- 劃分為兩個子序列
- 分別對每個子序列歸并排序
- 有序子序列合并
兩路歸并排序
#include<iostream> using namespace std;// 兩個有序子序列都從左向右掃描,歸并到新數組 template <class Record> void Merge(Record Array[], Record TempArray[], int left ,int right,int middle){int i,j,index1,index2;for(j=left;j<=right;j++)TempArray[j] = Array[j]; //放入臨時數組i = left;index1 = left;index2 = middle+1;while(index1<=middle && index2<=right){if(TempArray[index2] < TempArray[index1])Array[i++] = TempArray[index2++];elseArray[i++] = TempArray[index1++];}while(index1<=middle) //復制剩下的左序列Array[i++] = TempArray[index1++];while(index2<=right) //復制剩下的右序列Array[i++] = TempArray[index2++]; }template <class Record> void MergeSort(Record Array[], Record TempArray[], int left, int right) { // Array為待排序數組,left,right兩端int middle;if (left < right) { // 否則序列中只有0或1個記錄,不用排序middle = (left + right) / 2; // 平分為兩個子序列 // 對左邊一半進行遞歸 MergeSort(Array,TempArray,left,middle);// 對右邊一半進行遞歸MergeSort(Array, TempArray,middle+1,right);Merge(Array, TempArray,left,right,middle); // 歸并 } }int main(){int arr[] = {1,9,7,10,5,8,4,2,11,6};int tempArr[10];MergeSort(arr,tempArr,0,9);for(int i:arr)cout<<i<<" ";cout<<endl;return 1; } 復制代碼歸并算法優化
- 同優化的快速排序一樣,對基本已排序序 列直接插入排序
- R.Sedgewick優化:歸并時從兩端開始 處理,向中間推進,簡化邊界判斷
算法分析
- 空間代價O(n)
- 最大,最小,平均時間代價O(nlog n)
- 普通歸并排序是穩定的,因為合并的時候是按照順序合并。Sedgewick采用一個正序一個逆序合并所以相同值會顛倒順序。所以是不穩定的。
- Sedgewick算法需要更多地元素之間的比較,但是普通算法需要判斷邊界,所以比較次數更多。總體來說Sedgewick 算法更優。
分配排序和基數排序
- 不需要進行紀錄之間兩兩比較
- 需要事先知道記錄序列的一些 具體情況
桶排序
- 事先知道序列中的記錄都位于某個小區間段 [0, m) 內
- 將具有相同值的記錄都分配到同一個桶中,然后依次按照編號從桶中取出記錄,組成一個有序序列
算法分析
- 數組長度為 n, 所有記錄區間 [0, m) 上
- 時間代價:
- 統計計數:Θ(n+m) , 輸出有序序列時循環 n 次
- 總的時間代價為 Θ(m+n)
- 適用于m相對于n很小的情況,也就是適用于小區間,但m不要超過nlogn比較好,因為桶式排序的算法復雜度為O(n+m),如果m過大則換用其它方法比較好.
- 空間代價:
- m 個計數器,長度為 n 的臨時數組,Θ(m+n)
- 穩定
基數排序
- 桶式排序只適合 m 很小的情況
- 基數排序:當m很大時,可以將一個記錄的值即排序碼拆分為多個部分來進行比較
例子
例如:對 0 到 9999 之間的整數進行排序
- 將四位數看作是由四個排序碼決定,即千、百 、十、個位,其中千位為最高排序碼,個位為 最低排序碼。基數 r=10。
- 可以按千、百、十、個位數字依次進行4次桶 式排序
- 4趟分配排序后,整個序列就排好序了
低位優先法
- LSD,Least Significant Digit first
- 從最低位 k0 開始排序
- 對于排好的序列再用次低位 k1 排序;
- 依次重復,直至對最高位 kd-1 排好序后, 整個序列成為有序的
- 分、收;分、收;...;分、收的過程 – 比較簡單,計算機常用
基數排序的實現
- 主要討論 LSD(低位優先法)
- 基于順序存儲
- 基于鏈式存儲
- 原始輸入數組R的長度為n,基數為 r,排序碼個數為 d
基于數組的基數排序
template <class Record> void RadixSort(Record Array[], int n, int d, int r) {Record *TempArray = new Record[n];int *count = new int[r]; int i, j, k;int Radix = 1; // 模進位,用于取Array[j]的第i位 for(i=1;i<=d;i++) { //對第i個排序碼分配for (j = 0; j < r; j++)count[j] = 0; // 初始計數器均為0for(j=0;j<n;j++){ //統計每桶記錄數 k = (Array[j] / Radix) % r; // 取第i位 count[k]++; // 相應計數器加1}for (j = 1; j < r; j++) // 給桶劃分下標界 count[j] = count[j-1] + count[j];for(j=n-1;j>=0;j--) { //從數組尾部收集 k=(Array[j]/Radix)%r; //取第i位 count[k]--; // 桶剩余量計數器減1 TempArray[count[k]] = Array[j]; // 入桶}for (j = 0; j < n; j++) // 內容復制回 Array 中Array[j] = TempArray[j];Radix *= r; // 修改模Radix} } 復制代碼順序基數排序代價分析
- 空間代價:
- 臨時數組, n
- r 個計數器
- 總空間代價 Θ(n+r)
- 時間代價
- 桶式排序:Θ(n+r)
- d 次桶式排序
- Θ(d·(n+r))
基于靜態鏈的基數排序
- 將分配出來的子序列存放在 r 個 (靜 態鏈組織的) 隊列中
- 鏈式存儲避免了空間浪費情況
鏈式基數排序算法代價分析
- 空間代價
- n 個記錄空間
- r 個子序列的頭尾指針
- Θ(n + r)
- 時間代價
- 不需要移動記錄本身, 只需要修改記錄的 next 指針
- Θ(d·(n+r))
線性時間整理靜態鏈表
template <class Record> void AddrSort(Record *Array, int n, int first) {int i, j;j = first; // j待處理數據下標Record TempRec;for(i=0;i<n-1;i++) {//循環,每次處理第i個記錄TempRec = Array[j]; // 暫存第 i 個的記錄 Array[j] swap(Array[i], Array[j]);Array[i].next = j;j = TempRec.next; while (j <= i)j = Array[j].next; } } 復制代碼基數排序效率
- 時間代價為Θ(d·n), 實際上還是 Θ(nlog n)
- 基數排序中應該是d>=logrm,m為排序碼中的最大數。實際測試的時候當n>=10億時,在64bit系統下基數排序還是有優勢的。甚至比introsort還要快一些。只有排序碼中元素兩兩不相同時,才有m=n。
索引排序
在排序時,若是數據很復雜,對數據的移動顯然是費時的。若把數據移動改為索引(或指針)移動,則減少了操作復雜度。索引排序,也叫地址排序,就是這種排序思想。
索引含義
根據索引的含義不同,索引排序的算法上也主要分為兩種。
- index[i]為array[i]最終在有序序列中的位置。
- index[i]為位置i上最終應存放元素的下標。即最終元素按array[index[0]]、array[index[1]]……有序。
索引排序的適用性
- 一般的排序方法都可以
- 那些賦值(或交換)都換成對index 數組的賦值(或交換)
- 舉例:插入排序
插入排序的索引地址排序版本
template<class Record> void AddrSort(Record Array[], int n) {//n為數組長度 int *IndexArray = new int[n], TempIndex; int i,j,k;Record TempRec;for (i=0; i<n; i++)//只需一個臨時空間IndexArray[i] = i; for (i=1; i<n; i++)// 依次插入第i個記錄// 依次比較,發現逆置就交換for (j=i; j>0; j--)if ( Array[IndexArray[j]] <Array[IndexArray[j-1]])swap(IndexArray, j, j-1);else break;//此時i前面記錄已排序for(i=0;i<n;i++) { // 調整為按下標有序j= i;TempRec = Array[i];while (IndexArray[j] != i) {k=IndexArray[j]; Array[j]=Array[k]; IndexArray[j] = j; j = k;}Array[j] =TempRec; IndexArray[j] = j;} } 復制代碼總結
- 時間復雜度(試驗時間)
- 穩定性
- 對于穩定的排序算法,在排序前將數據隨機打亂,就可以達到不穩定的效果了。
- 對于不穩定的排序算法,可以將數據擴充出一個域,用于記錄初始下標,當數據關鍵值相同時,比較下標大小,這樣排序算法就是穩定的了。
總結
以上是生活随笔為你收集整理的内排序及时间复杂度分析-插入排序选择排序交换排序归并排序分配和索引排序对比...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mac下使用ABTestingGatew
- 下一篇: Linux服务器下的HTTP抓包分析