1. 程式人生 > >不懂演算法的程式設計師不是好工程師!

不懂演算法的程式設計師不是好工程師!

640?wx_fmt=gif

時刻提醒自己,技術之路修遠兮,予以自勉。

640?wx_fmt=jpeg

作者 |  菜鳥奮鬥史

責編 | 胡巍巍


640?wx_fmt=png

演算法主要衡量標準


時間複雜度(執行時間)

在演算法時間複雜度維度,我們主要對比較和交換的次數做對比,其他不交換元素的演算法,主要會以訪問陣列的次數的維度做對比。

其實有很多同學對於演算法的時間複雜度有點模糊,分不清什麼所謂的O(n),O(nlogn),O(logn)......等,也許下圖對一些人有一些更直觀的認識。

640?wx_fmt=jpeg

空間複雜度(額外的記憶體使用)

排序演算法的額外記憶體開銷和執行時間同等重要。就算一個演算法時間複雜度比較優秀,空間複雜度非常差,使用的額外記憶體非常大,筆者認為它也算不上一個優秀的演算法。

結果的正確性

這個指標是筆者自己加上的,我始終認為一個優秀的演算法最終得到的結果必須是正確的。就算一個演算法擁有非常優秀的時間和空間複雜度,但是結果不正確,又有什麼意義呢?


640?wx_fmt=png

原理


在起始位置右側(或左側)找出最小的那個元素,然後和起始位置的元素交換。選擇排序是一個不穩定的排序演算法。

具體步驟如下:

  • 在一個數據列表中找到最小的那個元素,將它和列表的第一個元素交換位置。

  • 在第二個元素位置開始再次尋找最小的那個元素,然後和列表的第二個位置的元素交換。

  • 在第三個元素位置開始再次尋找最小的那個元素,然後和列表的第三個位置的元素交換

  • 如此反覆,直到開始查詢起始位置到達列表末尾。

如果查詢過程中最小的元素就是起止位置的元素,那麼它就和它自己交換。

因為這種演算法總是在不斷的選擇剩餘元素中最小者,因此得名選擇排序。


640?wx_fmt=png

複雜度


時間複雜度

1.比較次數

對於長度為N的列表,選擇排序需要大約n² /2次比較.即:O(n²)平方級別。

2.交換次數

對於長度為N的列表,選擇排序需要大約N次交換.即:O(N) 線性級別。


640?wx_fmt=png

效能和特點


總體來說,選擇排序是一種比較簡單的排序演算法,很容易理解也很好用程式碼實現,當然他的特點也很明顯:

執行時間和資料初始狀態無關

為什麼這麼說呢?演算法進行中為了查詢最小的元素而遍歷列表並不能為下次遍歷帶來任何資訊,這個特性在大部分情況下是缺點。

如果一個數據列表初始狀態是有序的或者部分有序的,選擇排序仍然需要全部掃描一次和交換。因此和一個完全無序的列表排序所花的時間相差不大。

資料移動次數是最少的

每次交換隻會改變兩個列表元素,因此長度為N的列表只會發生N次交換,交換次數和列表的長度是線性關係,其他演算法都不具備這個特性。


640?wx_fmt=png

適用場景


由於選擇排序的對比次數在平方級別,但是移動次數線上性級別,所以當N比較小的時候比較適用。


640?wx_fmt=png

其他


為什麼選擇排序不穩定呢?

首先我們要明白演算法穩定是什麼意思呢?

在待排序的資料中,存在多個相同的資料,經過排序之後,他們的相對順序依舊保持不變,實際上就是說array[i]=array[j],i<j.就是array[i]在array[j]之前。

那麼經過排序之後array[i]依舊在array[j]之前,那麼這個排序演算法穩定,否則,這個排序演算法不穩定。

根據以上定義很容易可以得出這樣的結論:

我們舉出一個例項,序列5 8 5 2 9, 這個在執行選擇排序的時候,第一遍,肯定會將array[0]=5,交換到2所在的位置,也就是 2 8 5 5 9。

那麼很顯然,之後的排序我們就會發現,array[2]中的5會出現在原先的array[0]之前,所以選擇排序不是一個穩定的排序。


640?wx_fmt=png

實現案例


c#

  

static void Main(string[] args)

        
{

            List<int> data = new List<int>() ;

            for (int i = 0; i < 10; i++)

            {                

                data.Add(new Random(Guid.NewGuid().GetHashCode()).Next(1100));

            }

            //列印原始陣列值

            Console.WriteLine($"原始資料: {string.Join(",", data)}");

            int n = data.Count;

            //此處可以直接從第二個元素開始

            for (int i = 1; i < n; i++)

            {               

                //查詢最小的元素的索引

                for (int j = i; j>0  ; j--)

                {

                    if (data[j] < data[j - 1])

                    {

                        //異或法 交換兩個變數,不用臨時變數

                        data[j] = data[j] ^ data[j-1];

                        data[j-1] = data[j] ^ data[j - 1];

                        data[j ] = data[j] ^ data[j - 1];

                    }                    

                }

            }

            //列印排序後的陣列

            Console.WriteLine($"排序資料: {string.Join(",", data)}");

            Console.Read();

        }

執行結果:

  • 原始資料:97,85,61,22,62,12,67,22,68,42

  • 排序資料:12,22,22,42,61,62,67,68,85,97

作者簡介:一個奔走在通往網際網路更高之路的工程師,熱衷於網際網路技術。目前就職於某網際網路教育公司,應用服務端主要負責人。擁有10年+網際網路開發經驗。熱衷於高效能、高併發、分散式技術領域的研究。 主要工作語言為C#和Golang 。

推薦閱讀:

640?wx_fmt=gif

640?wx_fmt=gif