「算法」的入門,從「排序算法」開始,但願經過「排序算法」這一部分的學習,可以讓咱們認識到「算法」的威力,「算法」不只僅只存在與咱們的面試中(那時只是由於我不知道「算法」而已),「算法」無處不在,「算法」頗有用。java
下面是一些說明:python
一、會直接使用「空間複雜度」和「時間複雜度」的概念,不妨先有個印象,實在糾結的話,能夠去翻翻書,「空間複雜度」和「時間複雜度」最多的應用就在於比較不一樣算法的優劣;面試
二、「排序算法」這一章節爲了方便說明,使用的例子都是以「整數數組」爲例,而且是「升序排序」,學習過 Java 語言的朋友就知道,待排序的也能夠是對象,只要實現了相關的接口,實現了相應的比較規則,就能夠進行排序。算法
咱們選擇「選擇排序」做爲算法入門的開篇。理由以下:編程
一、「選擇排序」算法的思想十分簡單,很是接近咱們的思惟方式:先找最小的數、再找第 2 小的數,依次類推,最後剩下的就是數組中最大的元素;數組
二、「選擇排序」的實現也很簡單。編程語言
思想:不斷地選擇剩餘元素之中的最小者。函數
一、每一輪交換都能排定一個元素,交換的總次數是固定的;學習
說明:「交換的總次數」等於「元素的總數 - 1」,所以算法的時間複雜度取決於比較的次數;測試
二、運行時間和輸入無關,即:一個「已經有序」的數組、一個全部的元素都相等的數組、一個元素隨機排列的數組所用的排序時間是同樣的;
說明:後續咱們會編寫一些測試用例,比較不一樣的算法在不一樣的測試用例上的運行時間。這些測試用例中,就有如下 $3$ 種。
(1)一個「已經有序」的數組:例如:[4, 5, 6, 8, 9, 10]
,之後咱們學習的排序算法中,就有一種算法名叫「插入排序」就能檢測出數組是否是有序的,極端狀況下,「插入排序」算法看一遍數組中的元素,就知道數組已經有序了,後續就什麼都不用作了。而「選擇排序」得一遍又一遍看數組的元素好幾遍,「幾乎是」有多少個數,就會看數組多少遍,每一遍選出當前沒有排定元素中的最小者;
(2)一個全部的元素都相等的數組,例如:[6, 6, 6, 6, 6, 6]
;
(3)一個元素隨機排列的數組,就是咱們通常意義下,雜亂無序的數組,例如:[8, 18, 10, 6, 5, 4, 20]
。
三、數據移動是最少的。
這點應該說是「選擇排序」的優勢了,若是咱們的排序任務對交換操做很是敏感,不妨考慮「選擇排序」。
例如:咱們待排序的是碼頭上的集裝箱,交換集裝箱的成本是很高的,此時「選擇排序」就是最好的選擇。
小貼士:這一部份內容不須要記住,等到後面接觸了「插入排序」、「歸併排序」、「快速排序」等其它排序算法之後,再與「選擇排序」進行比較,就不難理解了。
Python 實現1:
def swap(nums, idx1, idx2): if idx1 == idx2: return temp = nums[idx1] nums[idx1] = nums[idx2] nums[idx2] = temp def select_sort(nums): """ 選擇排序,記錄最小元素的索引,最後才交換位置 :param nums: :return: """ l = len(nums) for i in range(l): min_index = i for j in range(i + 1, l): if nums[j] < nums[min_index]: min_index = j swap(nums, i, min_index)
說明:交換兩個數組中的元素,在 Python 中有更簡單的寫法,這是 Python 的語法糖,其它語言中是沒有的。
Python 實現2:主體部分和「Python 實現1」是同樣的。
def select_sort(nums): """ 選擇排序,記錄最小元素的索引,最後才交換位置 :param nums: :return: """ l = len(nums) for i in range(l): min_index = i for j in range(i + 1, l): if nums[j] < nums[min_index]: min_index = j nums[i], nums[min_index] = nums[min_index], nums[i]
這就是「選擇排序」算法。
若是你看到本身編寫的程序不正確,能夠在程序中增長打印輸出,幫助你調試程序:
分析:第 1 輪要看 n 個元素;
第 2 輪要看 n-1 個元素;
第 3 輪要看 n-2 個元素;
……
第 n 輪要看 1 個元素;
對它們求和,用等差數列的通項公式。不過其實你也不用計算它,「時間複雜度」的計算咱們只看次數最高的,因此「選擇排序」是平方時間複雜度。
分析:咱們在交換兩個數組元素位置的時候,使用了 1 個輔助的空間。
是否是以爲很簡單,後面難度會一點一點加上來。此時,咱們不妨作一些熱身的練習,咱們後面會用到。這些練習只是減輕一點咱們後面編寫測試用例的工做量,本身設計函數參數就好。
練習1:編寫三個函數,分別生成上文中提到的 3 種類型的數組,要求可以自定義生成數組的大小,這樣咱們之後編寫測試用例的時候,就可使用這些函數了。
練習2:編寫一個函數,判斷一個數組是不是升序排序。這個函數用於判斷咱們的算法是否正確。
如下補充的知識是針對零基礎的朋友們的,由於我也是零基礎過來的,以爲這些東西能夠說一下。
交換兩個變量的值,在排序中是常見的操做,而且也是程式化的,特別好記。先給出 Java 的寫法,再給出 Python 的寫法,最後給出「不是人的寫法」。
Java 寫法:
int temp = a; a = b; b = temp;
說明:這段代碼其實很好理解,要交換兩個變量的值,給要讓變量 a 把位置讓出來,即 int temp = a
,而後把另外一個變量 b 的值複製給 a,即 a = b
,最後把以前 a 放在 temp 裏的值賦給 b。這麼說比較拗口,但其實我每次寫這段代碼的時候,都不用想這個過程的。由於這段代碼有規律可循:首先引入一個輔助變量 temp,這是必要的,而後就開始「首尾相接」了,大家看一下,是否是這個特色,最後接回 temp,記住這個規律就能夠了。在 Python 中是這樣寫的:
Python 寫法1:
temp = a a = b b = temp
不過,Python 是一門神奇的編程語言,它提供了語法糖。
a, b = b, a
就能夠交換兩個變量的值,不妨動手驗證一下:
是否是很酷,Python 的寫法有的時候更像僞代碼,更符合人的思惟,但我沒有說 Python 更好的意思。其實 Python 解釋器在後臺也是引入了輔助變量完成兩個變量的交換。其實,交換兩個變量的值,有更高效的作法,下面給出兩個交換變量的代碼,這兩種方法都不用引入輔助變量,相信聰明的你必定不難理解。
這裏利用到了異或運算的特色:異或運算能夠理解成不進位的加法。那麼一個數兩次異或同一個數,就和原來的數相等。上面基於異或運算交換兩個變量的值就利用這個性質。若是你還不熟悉異或運算,不妨查閱一些資料。
前面咱們說到了,咱們爲了突出排序算法的思想,將全部的例子僅限在數組排序中。事實上 Java 和 Python 這些面向對象的編程語言都支持對象的排序,只要給它們定義相應的比較規則便可。有兩種方式,Python 和 Java 都是支持的:
(1)爲對象添加用於比較的函數
def __cmp__(self, other): pass
定義這個魔法函數,就可使用對象集合進行排序了。
Comparable
接口中的 compareTo
方法。若是你以爲給對象添加用於比較的函數,這種作法的侵入性比較強(由於修改了類),那麼你能夠在排序的方法中,傳入比較規則。
(2)在排序的方法中,傳入比較規則
Comparator
接口的對象。