優化的直接插入排序(二分查詢插入排序,希爾排序)
本博文向大家介紹了插入排序的三種實現:直接插入排序,二分查詢插入排序,希爾排序。詳細分析的其實現過程、時間複雜度和空間複雜度、穩定性以及優化改進策略。最後簡單的做了下效能測試。
直接插入排序
(一)概念及實現
直接插入排序的原理:先將原序列分為有序區和無序區,然後再經過比較和後移操作將無序區元素插入到有序區中。
具體如下(實現為升序):
設陣列為a[0…n]。
1.將原序列分成有序區和無序區。a[0…i-1]為有序區,a[i…n] 為無序區。(i從1開始)
2.從無序區中取出第一個元素,即a[i],在有序區序列中從後向前掃描。
3.如果有序元素大於a[i],將有序元素後移到下一位置。
4.重複步驟3,直到找到小於或者等於a[i]的有序元素,將a[i]插入到該有序元素的下一位置中。
5.重複步驟2~4,直到無序區元素為0。
實現程式碼:
public static void Sort<T>(IList<T> arr) where T : IComparable<T> { if (arr == null) throw new ArgumentNullException("arr"); int length = arr.Count(); if (length > 1) { int i, j, k; // 將arr分成有序區和無序區,初始有序區有一個元素 // 0-(i-1) 為有序區;i-(length-1)為無序區 (i從1開始) for (i = 1; i < length; i++) { T temp = arr[i]; // 邊找位置邊後移元素 for (j = i - 1; j >= 0 && arr[j].CompareTo(temp) > 0; j--) arr[j + 1] = arr[j]; // 如果已排序的元素大於新元素,將該元素移到下一位置 // 將 arr[i] 放到正確位置上 arr[j + 1] = temp; } } }
示例:
89,-7,999,-89,7,0,-888,7,-7
排序的過程:
[89][-7 999 -89 7 0 -888 7 -7]
[-7 89][999 -89 7 0 -888 7 -7]
[-7 89 999][-89 7 0 -888 7 -7]
……
……
[-888 -89 -7 -7 0 7 7 89 999] []
(二)演算法複雜度
1.時間複雜度:O(n^2)
直接插入排序耗時的操作有:比較+後移賦值。時間複雜度如下:
1)最好情況:序列是升序排列,在這種情況下,需要進行的比較操作需(
2)最壞情況:序列是降序排列,那麼此時需要進行的比較共有次。後移賦值操作是比較操作的次數加上 (n-1)次。即O(n^2)
3)漸進時間複雜度(平均時間複雜度):O(n^2)
2.空間複雜度:O(1)
從實現原理可知,直接插入排序是在原輸入陣列上進行後移賦值操作的(稱“就地排序”),所需開闢的輔助空間跟輸入陣列規模無關,所以空間複雜度為:O(1)
(三)穩定性
直接插入排序是穩定的,不會改變相同元素的相對順序。
(四)優化改進
1.二分查詢插入排序:因為在一個有序區中查詢一個插入位置,所以可使用二分查詢,減少元素比較次數提高效率。
2.希爾排序:如果序列本來就是升序或部分元素升序,那麼比較+後移賦值操作次數就會減少。希爾排序正是通過分組的辦法讓部分元素升序再進行整個序列排序。(原因是,當增量值很大時資料項每一趟排序需要的個數很少,但資料項的距離很長。當增量值減小時每一趟需要和動的資料增多,此時已經接近於它們排序後的最終位置。)
下面來分別介紹:二分查詢插入排序和希爾排序
二分查詢插入排序
(一)概念及實現
二分查詢插入排序的原理:是直接插入排序的一個變種,區別是:在有序區中查詢新元素插入位置時,為了減少元素比較次數提高效率,採用二分查詢演算法進行插入位置的確定。
具體如下(實現為升序):
設陣列為a[0…n]。
1.將原序列分成有序區和無序區。a[0…i-1]為有序區,a[i…n] 為無序區。(i從1開始)
2.從無序區中取出第一個元素,即a[i],使用二分查詢演算法在有序區中查詢要插入的位置索引j。
3.將a[j]到a[i-1]的元素後移,並將a[i]賦值給a[j]。
4.重複步驟2~3,直到無序區元素為0。
實現程式碼:
/// <summary> /// 二分查詢插入排序 /// </summary> public static void BinarySort<T>(IList<T> arr) where T : IComparable<T> { if (arr == null) throw new ArgumentNullException("arr"); int length = arr.Count(); if (length > 1) { int i, j, k; // 將arr分成有序區和無序區,初始有序區有一個元素 // 0-(i-1) 為有序區;i-(length-1)為無序區 for (i = 1; i < length; i++) { // 二分查詢在有序區尋找插入的位置 int index = BinarySearchIndex<T>(arr, i - 1, arr[i]); if (i != index) { T temp = arr[i]; // 後移元素,騰出arr[index]位置 for (j = i - 1; j >= index; j--) arr[j + 1] = arr[j]; // 將 arr[i] 放到正確位置上 arr[index] = temp; } } } } /// <summary> /// 二分查詢要插入的位置得Index /// </summary> /// <param name="arr">陣列</param> /// <param name="maxIndex">有序區最大索引</param> /// <param name="data">待插入值</param> /// <returns>插入的位置的Index</returns> private static int BinarySearchIndex<T>(IList<T> arr, int maxIndex, T data) where T : IComparable<T> { int iBegin = 0; int iEnd = maxIndex; int middle = -1; int insertIndex = -1; while (iBegin <= iEnd) { middle = (iBegin + iEnd) / 2; if (arr[middle].CompareTo(data) > 0) { iEnd = middle - 1; } else { // 如果是相同元素,也是插入在後面的位置 iBegin = middle + 1; } } return iBegin; }
示例:
89,-7,999,-89,7,0,-888,7,-7
排序的過程:
[89][-7 999 -89 7 0 -888 7 -7]
[-7 89][999 -89 7 0 -888 7 -7]
[-7 89 999][-89 7 0 -888 7 -7]
……
……
[-888 -89 -7 -7 0 7 7 89 999] []
(二)演算法複雜度
1.時間複雜度:O(n^2)
二分查詢插入位置,因為不是查詢相等值,而是基於比較查插入合適的位置,所以必須查到最後一個元素才知道插入位置。
二分查詢最壞時間複雜度:當2^X>=n時,查詢結束,所以查詢的次數就為x,而x等於log2n(以2為底,n的對數)。即O(log2n)
所以,二分查詢排序比較次數為:x=log2n
二分查詢插入排序耗時的操作有:比較