【算法日積月累】1-選擇排序

image-20190112135121340

「算法」的入門,從「排序算法」開始,但願經過「排序算法」這一部分的學習,可以讓咱們認識到「算法」的威力,「算法」不只僅只存在與咱們的面試中(那時只是由於我不知道「算法」而已),「算法」無處不在,「算法」頗有用。java

下面是一些說明:python

一、會直接使用「空間複雜度」和「時間複雜度」的概念,不妨先有個印象,實在糾結的話,能夠去翻翻書,「空間複雜度」和「時間複雜度」最多的應用就在於比較不一樣算法的優劣;面試

二、「排序算法」這一章節爲了方便說明,使用的例子都是以「整數數組」爲例,而且是「升序排序」,學習過 Java 語言的朋友就知道,待排序的也能夠是對象,只要實現了相關的接口,實現了相應的比較規則,就能夠進行排序。算法

咱們選擇「選擇排序」做爲算法入門的開篇。理由以下:編程

一、「選擇排序」算法的思想十分簡單,很是接近咱們的思惟方式:先找最小的數、再找第 2 小的數,依次類推,最後剩下的就是數組中最大的元素;數組

二、「選擇排序」的實現也很簡單。編程語言

1、經過具體例子理解「選擇排序」的思想

思想:不斷地選擇剩餘元素之中的最小者。函數

image-20190111200853617

「選擇排序」算法的特色

一、每一輪交換都能排定一個元素,交換的總次數是固定的;學習

說明:「交換的總次數」等於「元素的總數 - 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]

這就是「選擇排序」算法。

若是你看到本身編寫的程序不正確,能夠在程序中增長打印輸出,幫助你調試程序:

image-20190111201433272

2、時間複雜度與空間複雜度

時間複雜度:O(n^2)

分析:第 1 輪要看 n 個元素;

第 2 輪要看 n-1 個元素;

第 3 輪要看 n-2 個元素;

……

第 n 輪要看 1 個元素;

對它們求和,用等差數列的通項公式。不過其實你也不用計算它,「時間複雜度」的計算咱們只看次數最高的,因此「選擇排序」是平方時間複雜度。

空間複雜度:O(1)

分析:咱們在交換兩個數組元素位置的時候,使用了 1 個輔助的空間。

3、熱身練習

是否是以爲很簡單,後面難度會一點一點加上來。此時,咱們不妨作一些熱身的練習,咱們後面會用到。這些練習只是減輕一點咱們後面編寫測試用例的工做量,本身設計函數參數就好。

練習1:編寫三個函數,分別生成上文中提到的 3​ 種類型的數組,要求可以自定義生成數組的大小,這樣咱們之後編寫測試用例的時候,就可使用這些函數了。

練習2:編寫一個函數,判斷一個數組是不是升序排序。這個函數用於判斷咱們的算法是否正確。

4、補充知識

如下補充的知識是針對零基礎的朋友們的,由於我也是零基礎過來的,以爲這些東西能夠說一下。

一、交換兩個變量的值

交換兩個變量的值,在排序中是常見的操做,而且也是程式化的,特別好記。先給出 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 是一門神奇的編程語言,它提供了語法糖。

使用 Python 語法糖交換兩個變量的值

a, b = b, a

就能夠交換兩個變量的值,不妨動手驗證一下:

image-20190112140336597

是否是很酷,Python 的寫法有的時候更像僞代碼,更符合人的思惟,但我沒有說 Python 更好的意思。其實 Python 解釋器在後臺也是引入了輔助變量完成兩個變量的交換。其實,交換兩個變量的值,有更高效的作法,下面給出兩個交換變量的代碼,這兩種方法都不用引入輔助變量,相信聰明的你必定不難理解。

基於加減法交換兩個變量的值

image-20190112141022074

基於異或運算交換兩個變量的值

image-20190112141224873

這裏利用到了異或運算的特色:異或運算能夠理解成不進位的加法。那麼一個數兩次異或同一個數,就和原來的數相等。上面基於異或運算交換兩個變量的值就利用這個性質。若是你還不熟悉異或運算,不妨查閱一些資料。

二、Java 和 Python 語言中比較器的實現

前面咱們說到了,咱們爲了突出排序算法的思想,將全部的例子僅限在數組排序中。事實上 Java 和 Python 這些面向對象的編程語言都支持對象的排序,只要給它們定義相應的比較規則便可。有兩種方式,Python 和 Java 都是支持的:

(1)爲對象添加用於比較的函數

  • 在 Python 中,有一個魔法函數,實現它便可:
def __cmp__(self, other):
    pass

定義這個魔法函數,就可使用對象集合進行排序了。

  • 在 Java 中,實現 Comparable 接口中的 compareTo 方法。

若是你以爲給對象添加用於比較的函數,這種作法的侵入性比較強(由於修改了類),那麼你能夠在排序的方法中,傳入比較規則。

(2)在排序的方法中,傳入比較規則

  • 在 Python 中,比較規則能夠經過 lambda 表達式傳入:
  • 在 Java 中,能夠傳入一個實現了 Comparator 接口的對象。
相關文章
相關標籤/搜索