經典排序演算法 — C# 版(上)

K戰神發表於2019-01-18

提起排序,與我們的息息相關,平時開發的程式碼少不了排序。

經典的排序演算法又非常多,我們怎麼評價一個排序演算法的好壞呢?

其實可以這樣想,要細緻的比較排序演算法好壞,那我們就從多方面儘可能詳細的對比

 

一、效率方面

1、排序演算法的執行效率:最好、最壞、平均

2、 我們之前捨棄的時間複雜度的係數、常量、低階,在這裡需要拿回來

3、排序,免不了比較和移動

 

二、記憶體消耗方面

沒錯就是 演算法的空間複雜度,不過對於排序的空間複雜度來說,又賦予了新的名詞 — 原地排序。

顧名思義是 原地排序的肯定是消耗記憶體少,反之需要往外走幾步那就需要臨時申請記憶體了。

原地排序 = O(1)

 

三、演算法穩定性

字面意義就是不論怎麼擺弄,這個演算法穩定,不會對順序有影響。

上面這句話應該加上一個定語:對於擁有相同值的元素的前後順序不會發生改變。

舉個例子:有兩個物件,其中的金額欄位一樣,按照金額排序,經過演算法一頓折騰後,相同金額的物件先後順序不能發生改變。

 

講完評估排序演算法的優劣的幾個方面,那就直接看看我們平時常見的幾個經典演算法:

1、氣泡排序

圖例演示

> C#

 1          //排序 — 氣泡排序
 2         private static void BubbleSort(int[] source)
 3         {
 4             if (source.Length <= 1)
 5                 return;
 6 
 7             bool isChanged = false;
 8             for (int i = 0; i < source.Length; i++)
 9             {
10                 for (int j = 0; j < source.Length - i - 1; j++)
11                 {
12                     var left = source[j];
13                     var right = source[j + 1];
14                     Console.WriteLine("【比較】");
15                     if (left <= right)
16                         continue;
17 
18                     source[j] = right;
19                     source[j + 1] = left;
20                     isChanged = true;
21                     Console.WriteLine("{交換}");
22                 }
23                 if (!isChanged)
24                     break;
25             }
26             Printf(source);
27         }

 

Q:氣泡排序的時間演算法複雜度

A:最壞時間複雜度 — O(n^2):迴圈 n*n次

   最好時間複雜度 — O(n)    :迴圈 n次即可

   平均時間複雜度 — O(?)

           這裡我們使用概率來分析平均複雜度,情況比較複雜。

   我們使用一種新的概念來分析平均複雜度,這個就是 有序度。

           有序度:看作是向量,左<= 右

      逆序度:正好相反,左 >= 右

   滿有序度 = n*(n-1) / 2

   逆序度 = 滿有序度 - 有序度

 

對於 n 個資料來說,最壞情況時間複雜度的有序度是0,要交換 n*(n-1)/2次才能正確輸出。

對於最好情況複雜度的有序度是n*(n-1)/2,需要交換0次就能達到完全有序。

最壞 n*(n-1)/2次,最好0次,取箇中間值來表示中間情況,也可以看作是平均情況 n*(n-1) /4

所以平均下來 要做 n*(n-1) / 4 次才能有序,因為氣泡排序的時間複雜度的上限是 O(n^2)

所以平均情況時間複雜度為 O(n^2)

雖然這樣推論平均個情況並不嚴格,但是比起概率推論來說,這樣簡單且有效。

 

Q:氣泡排序是不是原地排序

A:是,臨時變數為了交換資料,常量級別的臨時空間申請,所以空間複雜度為O(1)

 

Q:氣泡排序是不是穩定排序

A:是,因為沒有改變相同元素的先後順序。

 

 2、插入排序

假定,我們將排序串分為兩個區:已排序區,未排序區

一個元素要找到正確的的位置進行插入,那麼需要去已排序區域找到自己的位置後,

將這個位置的元素們向後移動,空出位置,然後新元素入坑。

從以上這個思路來看,插入排序也是涉及到了元素的比較和移動。

給我們一個無序陣列,哪塊是已排序區?哪裡是未排序區?

 

比如:9, 0, 1, 5, 2, 3, 6

初始時,9 就是已排序區域;

 0開始去已排序區域挨個比較,即 i=1,0<9,9向後挪動,空出位置,0入坑;

1開始去 [ 0,9 ] 已排序區域比較,1 < 9,9向後移動騰位置,1入坑,1 > 0 無需操作;

依次重複以上操作,即可達成有序。

 

圖例演示

 

> C#

 1         //排序 — 插入排序
 2         private static void InsertionSort(int[] source)
 3         {
 4             if (source == null || source.Length <= 0)
 5                 return;
 6 
 7             for (int i = 1; i < source.Length; i++)
 8             {// 未排序區
 9                 var sorting = source[i];
10                 int j = i - 1;
11 
12                 for (; j >= 0; j--)
13                 {// 已排序區
14 
15                     // 比較
16                     if (sorting >= source[j])
17                     {
18                         break;
19                     }
20 
21                     // 後移
22                     source[j + 1] = source[j];
23                 }
24 
25                 // 入坑
26                 source[j + 1] = sorting;
27             }
28             Printf(source);
29         }

 

Q:插入排序的時間演算法複雜度

A:最壞時間複雜度 — O(n^2):完全倒序,迴圈n次,比較n次

   最好時間複雜度 — O(n):完全有序,迴圈n次跳出

   平均時間複雜度 — O(n^2):迴圈 n次資料,在一個陣列中插入資料的平均情況時間複雜度為O(n),所以是 O(n^2)

 

Q:插入排序是不是原地排序

A:是,沒有臨時變數申請,所以空間複雜度為O(1)

 

Q:插入排序是不是穩定排序

A:是, if (sorting >= source[j]) 這個判斷保證了相同元素的先後順序不變,

         去掉等於號也可以發生改變。可以實現穩定排序所以說是穩定排序

 

開始我們也說了,這麼多排序演算法,我們要對比一下,擇優選擇。

排序 最好情況 最壞情況 平均情況 是否穩定 是否原地
冒泡 O(n) O(n^2) O(n^2)
插入 O(n) O(n^2) O(n^2)

 

 

 

 

那麼問題來了平均都是 O(n^2),為什麼傾向於使用插入排序呢?

這兩種排序我們將常量都放進來會發現,冒泡使用的常量數比排序多,所以在資料量上來後  常量*n 會有很大的差距。

我們的程式語言中的排序演算法很多都會傾向於插入排序演算法。

 

3、選擇排序

其實操作類似於插入排序,只不過是換了換操作方式。

所以也分為 已排序區和未排序區,操作方式是在未排序區間找到最小的,然後放到已排序區間最後。

圖例:

 

> C#

        private static void SelectionSort(int[] source)
        {
            if (source.Length <= 1)
                return;

            for (int i = 0; i < source.Length - 1; i++)
            {// 已排序
                var minIndex = i;
                
                for (int j = i+1; j < source.Length; j++)
                {//未排序

                    if (source[minIndex] > source[j])
                    {
                        minIndex = j;
                    }
                }
                if (i != minIndex)
                {
                    int tmp = source[i];
                    source[i] = source[minIndex];
                    source[minIndex] = tmp;
                }
            }

            Printf(source);
        }

 

Q:選擇排序的時間演算法複雜度

A:最壞時間複雜度 — O(n^2)

   最好時間複雜度 — O(n^2)

   平均時間複雜度 — O(n^2)

 

Q:選擇排序是不是原地排序

A:是,沒有臨時變數申請,所以空間複雜度為O(1)

 

Q:選擇排序是不是穩定排序

A:不是

 

4、對比 隨機生成1000個元素的 int 陣列

分別執行時間如下:

 

相關文章