Haskell常見排序演算法的實現

lyyyuna的部落格發表於2014-11-29

這篇文章嘗試使用 Haskhell 來重寫常見的排序演算法。這裡不考慮效率,比如時間和空間上的,所以不會刻意去寫成尾遞迴

插入排序

插入排序是一種簡單易懂的排序。這裡分為兩個步驟:

  1. 將一個元素插入一個已被排序的數列
  2. 對一個未排序的數列不停施以步驟 1

首先步驟 1,要插入數 x,當前序列中第一個數為 y。將 x, y 較小的數放在前面,然後對去除第一個數之後的子序列不停重複上述過程。

insert :: Ord a => a -> [a] -> [a]
insert x [] = [x]
insert x (y:ys) 
        |  x < y = x:y:ys
        | otherwise = y : insert x ys

接下來,只要施以步驟 2 即可,即將亂序的元素一個個地使用 insert 函式到另一個有序列表裡就可以了。

insertSort :: Ord a => [a] -> [a]
insertSort [] = []
insertSort (x:xs) = insert x (insertSort xs)

也可以寫成尾遞迴的形式,用一個列表來儲存中間結果:

insertSort :: Ord a => [a] -> [a] -> [a]
insertSort xs [] = xs
insertSort xs (y:ys) = insertSort (insert y xs) ys

氣泡排序

氣泡排序也分為兩個步驟:

  1. 比較相鄰元素的大小,然後交換較小的元素,將最大的數通過這個方式交換到最後
  2. 重複步驟 1

第一步是交換

swaps :: Ord a => [a] -> [a]
swaps [] = []
swaps [x] = [x]
swaps (x1:x2:xs)
        | x1 > x2 = x2 : swaps(x1:xs)
        | otherwise = x1 : swaps(x2:xs)

然後就是不停 swaps,直到列表不再發生變化

bubbleSort :: Ord a => [a] -> [a]
bubbleSort xs
        | swaps xs == xs = xs       -- 沒發生變化,就停止
        | otherwise = bubbleSort $ swaps xs

可以看到,第二步的效率不高,因為第一輪的 swaps 之後,最後一個數已經是最大的數了,第二步就沒有必要來遍歷到最後一個數。所以,可以將前一步 swaps 之後的序列分為前 n-1 項和最後一項,當前步下,最後一項可以不動,只需 bubbleSort 前 n-1 項。

bubbleSort' :: Ord a=> [a] -> [a]
bubbleSort' [] = []
bubbleSort' xs = bubbleSort' initElem ++ [lastElem]
        where 
            swappedElem = swaps xs
            initElem = init swappedElem
            lastElem = last swappedElem

選擇排序

首先找到最小的元素,將其從序列中取出,放入另一個序列中(初始為空),然後依次類推,直到所有元素從元序列被取出。

  1. 尋找序列中最小數,Haskell 有現成的函式 minimum
  2. 將最小數從原序列中刪除

這裡只要寫一個將序列中指定元素刪除的程式

deleteFromOri :: Eq a => a -> [a] -> [a]
deleteFromOri _ [] = []
deleteFromOri x (y:ys)
        | x == y = ys
        | otherwise = y:deleteFromOri x ys

然後只要將每次 minimum 得到的數從原序列刪除放入新序列

selectSort :: Ord a => [a] -> [a]
selectSort [] = []
selectSort xs = mini : selectSort xs'
        where 
            mini = minimum xs
            xs' = deleteFromOri mini xs

快速排序

快排的定義其實非常簡單,但在 c 語言中卻不好理解,不像 Haskell 這樣寫起來就像在定義一個數學定理一樣。

  1. 取出序列中的一個數(簡單的取法,直接取第一個元素),將所有小於該數的數作為一組放於該數左邊,將所有該數的數作為另一組放於該數右邊
  2. 對左右兩組數分別施以步驟 1

程式碼為

quickSort :: Ord a => [a] -> [a]
quickSort [] = []
quickSort [x:xs] = quickSort mini ++ [x] quickSort maxi
        where
            mini = filter (<x) xs
            maxi = filter (>=x) xs

當然這裡效果不高,會運算過程中會產生許多 []。

歸併排序

歸併排序這裡仍然是兩個步驟。

  1. 將兩個有序數列合為一個有序數列
  2. 將原序列不停劃分兩部分,直至每部分只有一個元素,然後不停呼叫步驟 1,將其合併成一個有序數列

步驟 1 的實現,只要將兩個序列 xs 和 ys 的第一個元素作比較即可。步驟 2 採用對半劃分。

merge :: Ord a => [a] -> [a] -> [a]
merge xs [] = xs
merge [] ys = ys
merge (x:xs) (y:ys)
        | x > y = y:merge (x:xs) ys
        | otherwise = x:merge xs (y:ys)

mergeSort :: Ord a => [a] -> [a]
mergeSort xs = merge (mergeSort x1) (mergeSort x2)
        where
            (x1, x2) = split xs
            split xs = (take mid xs, drop mid xs)
            mid = (length xs) `div` 2

相關文章