1. 程式人生 > >優化的直接插入排序(二分查詢插入排序,希爾排序)

優化的直接插入排序(二分查詢插入排序,希爾排序)

   本博文向大家介紹了插入排序的三種實現:直接插入排序,二分查詢插入排序,希爾排序。詳細分析的其實現過程、時間複雜度和空間複雜度、穩定性以及優化改進策略。最後簡單的做了下效能測試。

直接插入排序

(一)概念及實現

直接插入排序的原理:先將原序列分為有序區和無序區,然後再經過比較和後移操作將無序區元素插入到有序區中。

具體如下(實現為升序)

設陣列為a[0n]

1.將原序列分成有序區和無序區。a[0…i-1]為有序區,a[i…n] 為無序區。(i1開始)

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)最好情況:序列是升序排列,在這種情況下,需要進行的比較操作需(

n-1)次。後移賦值操作為0次。即O(n)

2)最壞情況:序列是降序排列,那麼此時需要進行的比較共有次。後移賦值操作是比較操作的次數加上 (n-1)次。即O(n^2)

3)漸進時間複雜度(平均時間複雜度):O(n^2)

2.空間複雜度:O(1)

從實現原理可知,直接插入排序是在原輸入陣列上進行後移賦值操作的(稱“就地排序”),所需開闢的輔助空間跟輸入陣列規模無關,所以空間複雜度為:O(1)

(三)穩定性

直接插入排序是穩定的,不會改變相同元素的相對順序。

(四)優化改進

1.二分查詢插入排序:因為在一個有序區中查詢一個插入位置,所以可使用二分查詢,減少元素比較次數提高效率。

2.希爾排序:如果序列本來就是升序或部分元素升序,那麼比較+後移賦值操作次數就會減少。希爾排序正是通過分組的辦法讓部分元素升序再進行整個序列排序。(原因是,當增量值很大時資料項每一趟排序需要的個數很少,但資料項的距離很長。當增量值減小時每一趟需要和動的資料增多,此時已經接近於它們排序後的最終位置。)

下面來分別介紹:二分查詢插入排序和希爾排序

二分查詢插入排序

(一)概念及實現

二分查詢插入排序的原理:是直接插入排序的一個變種,區別是:在有序區中查詢新元素插入位置時,為了減少元素比較次數提高效率,採用二分查詢演算法進行插入位置的確定。

具體如下(實現為升序)

設陣列為a[0n]

1.將原序列分成有序區和無序區。a[0…i-1]為有序區,a[i…n] 為無序區。(i1開始)

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

二分查詢插入排序耗時的操作有:比較