插入排序
(defun insertion-sort (arr symbol)
"
arr is of type LIST
symbol must be `>` or `<`
執行時間 執行次數
for j = 1 until arr.length c1 n
key = arr[j] c2 n - 1
i = j - 1 c4 n - 1
n
while i >= 0 and arr[i] > key c5 Σ Tj
j=1
n
arr[i + 1] = a[i] c6 Σ (Tj - 1)
j=1
n
i = i - 1 c7 Σ (Tj - 1)
j=1
a[i + 1] = key c8 n - 1
"
(let ((len (length arr)))
(do ((j 1 (1+ j)))
((>= j len) arr)
(let ((key (nth j arr)))
(do ((i (- j 1) (1- i)))
((not (and (>= i 0) (funcall symbol (nth i arr) key))) (setf (nth (+ i 1) arr) key))
(setf (nth (+ i 1) arr) (nth i arr)))))))
/**
* insert sort
*
* @param arr
* @return sorted array
*/
def `insert-sort` (arr: Array[Int]): Array[Int] = {
for (i <- 0 until arr.length) {
var j = i
val k = arr(j)
while (j > 0 && arr(j) > arr(j - 1)) {
arr(j) = arr(j - 1)
arr(j - 1) = k
j = j - 1
}
}
arr
}
歸并排序
; 分治法思想:
; 將原問題分解為幾個規模較小但類似于原問題的子問題
; 遞歸求解這些子問題
; 最后合并這些子問題的解來簡歷原問題的解
; 并歸排序算法完全遵守分治模式,即
; 分解:分解待排序的n個元素的序列成各具n/2個元素的兩個子序列
; 解決:使用并歸排序遞歸地排序兩個子序列
; 合并:合并兩個已排序的子序列以產生已排序的答案
; 而它的關鍵操作就是“合并”:兩個'已排序'序列的合并
; 下面,可以定義一個輔助函數merge(arr, l, m , r)來完成合并
(defun ~merge (arr l m r)
"
`包CL已經含有merge名稱的函數 此處需要換一個名稱`
arr(ay) - 數組
l - left 下標
m - mid 下標
r - right 下標
MERGE(array, l, m, r)
n1 = m - l + 1
n2 = r - m
let al[n1] and ar[n2] be new arrays
for i = 0 until n1
al[i] = array[l + i]
for j = 0 until n2
ar[j] = array[m + j + 1]
i = 0, j = 0
for k = l to r
case i < n1 and j < n2
if al[i] <= ar[j]
array[k] = al[i]
i = i + 1
else
array[k] = ar[j]
j = j + 1
case i < n1
array[k] = al[i]
i = i + 1
case j < n2
array[k] = ar[j]
j = j + 1
"
(let* ((n1 (+ 1 (- m l)))
(n2 (- r m))
(n3 (+ n1 n2))
(al (make-array `(,n1) :initial-element nil))
(ar (make-array `(,n2) :initial-element nil)))
(dotimes (i n1)
(setf (svref al i) (aref arr (+ i l))))
(dotimes (j n2)
(setf (svref ar j) (aref arr (+ j m 1))))
(let ((i 0)
(j 0))
(do ((k l (1+ k)))
((>= k (+ n3 l)) arr)
(cond ((and (< i n1) (< j n2))
(if (<= (aref al i) (aref ar j))
(progn
(setf (aref arr k) (aref al i))
(setf i (+ i 1)))
(progn
(setf (aref arr k) (aref ar j))
(setf j (+ j 1)))))
((< i n1)
(progn
(setf (aref arr k) (aref al i))
(setf i (+ i 1))))
((< j n2)
(progn
(setf (aref arr k) (aref ar j))
(setf j (+ j 1)))))))))
; 在~merge函數中,3個for循環運行時間都跟數組長度相關
; 其余步驟可用常量c代替
; 所以次函數時間復雜度可以簡化成
; c1 * n1 + c2 * n2 + c3 * n3 + c ==> 線性階 ==> O(n)
(defun merge-sort (arr l r)
"
歸并排序數組A[l ... r]
如果l >= r,則表示子數組最多只有一個元素,可以當作已排列數組序列
MERGE-SORT(arr, l, r)
if l < r
m = |(l + r) / 2|
MERGE-SORT(arr, l, m)
MERGE-SORT(arr, (m + 1), r)
MERGE(arr, l, m, r)
"
(if (< l r)
(let ((m (floor (+ l r) 2)))
(merge-sort arr l m)
(merge-sort arr (+ m 1) r)
(~merge arr l m r))))
/**
* 數組arr以下標m為分界點,[1 - m](m - r],必須有序一致
* @param arr
* @param l
* @param m
* @param r
* @return sorted array
*/
def merge (arr: Array[Int], l: Int, m: Int, r: Int): Array[Int] = {
val n1 = m - l + 1
val n2 = r - m
val left = new Array[Int](n1)
val right = new Array[Int](n2)
for (i <- 0 until n1)
left(i) = arr(l + i)
for (j <- 0 until n2)
right(j) = arr(m + 1 + j)
var i = 0
var j = 0
var end = 0
for (k <- l to r) {
end match {
case 1 => {
arr(k) = right(j)
j = j + 1
}
case 2 => {
arr(k) = left(i)
i = i + 1
}
case _ => {
if (i > n1 - 1) {
arr(k) = right(j)
j = j + 1
end = 1
} else if (j > n2 - 1) {
arr(k) = left(i)
i = i + 1
end = 2
} else if (left(i) < right(j)) {
arr(k) = left(i)
i = i + 1
} else {
arr(k) = right(j)
j = j + 1
}
}
}
}
arr
}
def `merge-sort` (arr: Array[Int], l: Int, r: Int): Array[Int] = {
if (l < r) {
val m: Int = math.floor((l + r) / 2).toInt
`merge-sort`(arr, l, m)
`merge-sort`(arr, m + 1, r)
merge(arr, l, m, r)
}
arr
}
堆排序
;;;; 6 堆排序
;;;; 6.1 堆
; 如果輸入數組中僅有常數個元素需要在排序過程中存儲在數組外
; 則稱排序算法是原址的(in place)
; 插入排序是一種原址排序
; 而歸并排序中, merge過程并不是原址的
; 堆排序是原址的,時間復雜度為O(nlgn),它具有插入排序和歸并排序的優點
(defun parent (i)
"根據節點下標求父節點 i [0 ...]
"
(if (= i 0)
0
(floor (/ (- i 1) 2))))
(defun left (i)
"i [0 ...]"
(+ (* 2 i) 1))
(defun right (i)
"i [0 ...]"
(* 2 (+ i 1)))
; 二叉堆的兩種形勢:
; 最大堆:除了根以外的所有節點i都滿足 A[parent(i)] >= A[i]
; 最小堆:除了根以外的所有節點i都滿足 A[parent(i)] <= A[i]
; 堆排序算法中,一般使用最大堆,最小堆通常用于有限隊列
; 如果把堆看成一棵樹,如果包含n個元素的堆可以看成一棵完全二叉樹
; 那么該堆的高度就是lgn,堆結構上的一些基本操作的運行時間至多與樹的高度成正比
; 即時間復雜度為O(lgn)
;;;; 6.2 維護堆的性質
(defun max-heapify (arr i &optional (root-index 0))
"
用于維護最大堆性質的重要過程
arr - 數組
i - 下標(相對數組, 以起始下標為準的新數組)
root-index - 起始下標(整個數組)
輸入數組arr和下標i
調用該函數時,假設父節點left(i)和right(i)的二叉樹都是最大堆
但A[i]可能小于其孩子,這就違背了最大堆的性質
MAX-HEAPIFY通過讓A[i]的值在最大堆中逐漸降級
從而使得以下標i為根節點的子樹重新遵守最大堆的性質
MAX-HEAPIFY (A, i, root-index)
l = left(i) + root-index
r = right(i) + root-index
heap-size = A.heap-size - root-index
new-i = i + root-index
if l - root-index < heap-size and A[l] > A[new-i]
largest = l
else
largest = new-i
if r - root-index < heap-size and A[r] > A[largest]
largest = r
if largest != new-i
exchange A[new-i] with A[largest]
MAX-HEAPIFY (A, largest, root-index)
對于一個以i為根節點,大小為n的子樹,MAX-HEAPIFY的時間代價包括:
調整A[i]/A[left(i)]/A[right(i)]的關系的時間代價O(1)
在加上一棵以i的一個孩子節點為根節點的子樹運行MAX-HEAPIFY的時間代價(假設遞歸調用會發生)
因為每個孩子子樹的大小至多為2n/3(最壞情況發生在樹的最底層恰好半滿的時候)
T(n) <= T(2n/3) + O(1) ==> T(n) = O(lgn)
"
(let ((heap-size (- (length arr) root-index))
(l (+ (left i) root-index))
(r (+ (right i) root-index))
(new-i (+ i root-index))
(largest (+ i root-index))
(dummy nil))
(if (and (< (- l root-index) heap-size) (> (aref arr l) (aref arr new-i)))
(setf largest l))
(if (and (< (- r root-index) heap-size) (> (aref arr r) (aref arr largest)))
(setf largest r))
(if (/= largest new-i)
(progn
(setf dummy (aref arr new-i))
(setf (aref arr new-i) (aref arr largest))
(setf (aref arr largest) dummy)
(max-heapify arr largest root-index))
arr)))
#+test
(let ((arr (vector 16 4 10 14 7 9 3 2 8 1)))
; arr result vector(16 14 10 8 7 9 3 2 4 1)
(max-heapify arr 1))
#+test
(let ((arr (vector 16 4 10 14 7 9 3 2 8 1)))
; arr result vector(16 4 10 14 7 9 3 8 2 1)
(max-heapify arr 0 7))
;;;; 6.3 建堆
(defun build-max-heap (arr root-index)
;i為相對數組里的下標,取值范圍[0 ... ]
(do ((i (- (length arr) 1) (1- i)))
((< i 0) arr)
(max-heapify arr i root-index)))
#+test
(let ((arr (vector 4 1 3 2 16 9 10 14 8 7)))
; result: (vector 16 14 10 8 7 9 3 2 4 1)
(build-max-heap arr))
#+test
(let ((arr (vector 4 1 3 2 16 9 10 14 8 7)))
; result: (vector 4 1 3 2 16 9 14 10 8 7)
(build-max-heap arr 5))
; 每次調用 max-heapify的時間復雜度是lgn
; build-max-heap需要O(n)次這樣的調用
; 因此總的時間復雜度就是O(nlgn)
; 這個上界雖然正確 但不是漸進緊確的
; 但是根據如下性質可以得到一個更緊確的界:
; 包含n個元素的堆的高度為floor(lgn),高度為h的堆最多包含celing(n/2^(h+1))個節點
; 最終可以推導出 build-max-heap為O(n)
; 目前不太清楚 build-max-heap => O(n) --!
;;;; 6.4 堆排序算法
(defun heap-sort (arr)
"
build-max-heap初始化整體數組為最大堆
在相對數組里,首元素為最大,每次維護首元素下標后的元素的最大堆性質
HEAP-SORT(A)
BUILD-MAX-HEAP(A, 0)
for i = 0 until A.length
MAX-HEAPIFY(A, 0, i)
"
;(do ((i 0 (1+ i)))
; ((>= i (length arr)) arr)
; (build-max-heap arr i)) ; 時間復雜度 O(n^2),棄用
(build-max-heap arr 0) ;O(n) or O(nlgn),但最終對heap-sort的時間復雜度無影響
(do ((i 1 (1+ i)))
((>= i (length arr)) arr)
(max-heapify arr 0 i)) ;O(nlgn)
)
/*
* 用數組存儲二叉堆,用下標計算表示出對應的父結點,左孩子,右孩子
*/
def parent(i: Int): Int = {
i match {
case i: Int if i <= 0 => 0
case _ => math.floor((i - 1) / 2).toInt
}
}
def left(i: Int): Int = (i * 2) + 1
def right(i: Int): Int = (i + 1) * 2
def exchange (arr: Array[Int], i: Int, j: Int) {
val tmp = arr(i)
arr(i) = arr(j)
arr(j) = tmp
}
/**
* 最大堆
* @param arr 數組
* @param i 相對下標,以根下標為準的新數組
* @param root 根下標
* @return
*/
def `max-heapify` (arr: Array[Int], i: Int, root: Int = 0): Array[Int] = {
val l = left(i) + root
val r = right(i) + root
val heapsize = arr.length - root
val index = i + root
var largest = index
if (l - root < heapsize && arr(l) > arr(largest))
largest = l
if (r - root < heapsize && arr(r) > arr(largest))
largest = r
if (largest != index) {
exchange(arr, index, largest)
`max-heapify`(arr, largest, root)
}
arr
}
def `build-max-heap` (arr: Array[Int], root: Int = 0): Array[Int] = {
for (i <- arr.length - 1 to 0 by -1) {
`max-heapify`(arr, i)
}
arr
}
def `heap-sort` (arr: Array[Int]): Array[Int] = {
`build-max-heap`(arr)
for (i <- 0 to arr.length - 1) {
`max-heapify`(arr, 0, i)
}
arr
}
快速排序
;;;; 快速排序
; 快速排序的最壞時間復雜度為O(n^2),雖然最壞時間復雜度很差
; 但在實際排序應用中是最好的選擇
; 它的期望復雜度是nlgn,而且也是原址排序
;;;; 7.1 快速排序描述
; 與歸并排序一樣,它采用了分治思想,下面是快速排序對數組A[l ... r]三步分治過程:
; 分解:數組A[l...r]被劃分成為兩個(可能為空)子數組A[l...m-1]和A[m+1...r]
; 使得A[l...m-1]中的任一元素都小于等于A[m],而A[m]也小于等于A[m+1...r]中的任一元素
; 其中,計算下標m也是劃分的一部分
; 解決:通過遞歸調用快速排序,對子數組A[l...m-1]和A[m+1...r]進行排序
; 合并:因為子數組都是原址排序的,所以不需要合并操作:數組A[l...r]已經有序
(defun quick-sort (arr l r)
"
QUICK-SORT(A, l, r)
if l < r
m = PARTITION(A, l, r)
QUICK-SORT(A, l, m - 1)
QUICK-SORT(A, m + 1, r)
"
(let ((m nil))
(if (< l r)
(progn
(setf m (partition arr l r))
(quick-sort arr l (- m 1))
(quick-sort arr (+ m 1) r)))
arr))
(defun partition (arr l r)
"
PARTITION(A, L, R)
k = A[R]
i = L
for j = L to R - 1
if A[j] <= x
exchange A[i] with A[j]
i = i + 1
exchange A[i] with A[R]
return i
大致思路就是這樣,數組A[L...I...J...R]
區間[L...I]存放比key小或者等于的元素
區間[I...J]存放比key大的元素
區間[J...R]是未比較元素
當J接近到R時,由于最終有一步是i = i + 1
所以下標i此時的元素是大于key值的,交換他們之后
數組就變成這樣的情況:
以下標i為界,前面部分都是比它小或者等的,后面部分都是大于它的
"
(let ((k (aref arr r))
(i l)
(dummy nil))
(do ((j l (1+ j)))
((>= j r))
(if (< (aref arr j) k)
(progn
(setf dummy (aref arr i))
(setf (aref arr i) (aref arr j))
(setf (aref arr j) dummy)
(setf i (+ i 1)))))
(setf dummy (aref arr i))
(setf (aref arr i) (aref arr r))
(setf (aref arr r) dummy)
(values i arr)))
;;;; 7.3 快速排序隨機化版本
; 前面的快速排序中,輸入數據的所有排列都是等概率的,但在實際工作中
; 這種情況不會總成立,所以在算法中引入隨機化性,使得所有輸入輸入都能獲得較好的期望性能
; 下面是隨機版本的快速排序的偽代碼實現:
; RANDOMIZED-PARTITION(A, p, r)
; i = RANDOM(p, r)
; exchange A[r] with A[i]
; return PARTION(A, p, r)
;
; RANDOMIZED-QUICKSORT(A, p, r)
; if p < r
; q = RANDOMIZED-PARTITION(A, p, r)
; RANDOMIZED-QUICKSORT(A, p, q - 1)
; RANDOMIZED-QUICKSORT(A, q + 1, r)
def partition (arr: Array[Int], left: Int, right: Int): Int = {
val k = arr(right)
var i = left
for (j <- left to right - 1) {
if (arr(j) <= k) {
/*exchange arr(i) and arr(j)*/
val tmp = arr(i)
arr(i) = arr(j)
arr(j) = tmp
i = i + 1
}
}
arr(right) = arr(i)
arr(i) = k
i
}
def `quick-sort`(arr: Array[Int], left: Int, right: Int): Array[Int] = {
if (left < right) {
val mid = partition(arr, left, right)
`quick-sort`(arr, left, mid - 1)
`quick-sort`(arr, mid + 1, right)
}
arr
}
計數排序
; 在排序的最終結果中,各元素之間的次序依賴于它們的比較關系,這樣的排序算法稱為`比較排序`
; 在之前筆記中涉及到的算法,包括`插入,歸并,快速,堆`排序,都屬于比較排序
; 計數排序運用的是運算而不是比較來確定排序順序的
;;;; 8.2 計數排序
; 計數排序基本思想:對于每一個輸入元素,確定小于x的元素的個數
; 利用這一信息,可以直接把元素放在數組對應的下標中
(defun counting-sort (arr)
"
COUNTING-SORT(A, B, k)
//數組B存放排序的輸出
let C[0...k] be a new array //提供臨時存儲空間
for i = 0 until k
C[i] = 0 //初始化C的元素
for j = 0 until A.length
C[A[j]] = C[A[j]] + 1 //C[i]代表A[n]元素個數(A[n] == i,i = 0, 1 ... k)
for i = 0 until k
C[i+1] = C[i+1] + C[i] //對每一個i=0,1...k,統計多少輸入元素是小于或等于i的
for j = A.length - 1 downto 0
B[C[A[j]] - 1] = A[j]
C[A[j]] = C[A[j]] - 1 //把每個元素A[j]放到它在輸出數值B中的正確位置
//如果所有元素互異,對于每一個A[j]值來說
//C[A[j]]就是A[j]在輸出數值中的最終正確位置
//這是因為有C[A[j]]個元素小于或等于A[j]
//但有可能所有元素不是互異的
//所以將每一個值A[j]放入B中后,都要將C[A[j]]值減一
//這樣,如果遇到下一個值等于A[j]的元素時
//可以直接放在輸出數組B中A[j]的前一個位置
計數排序的一個重要性質是它是穩定的:具有相同值的元素在輸出數組中的相對次序與它們在輸入數組中的相對次序相同
代碼實現上用到了比較性質,用于確定k值
而且由于編程語言的特性
加上對偏移的計算
實現上跟偽代碼略微不同
顯然,每重循環都是線性時間,最終時間復雜度表示為O(n)
但不是原址排序,空間換時間
如果計算出來的k值過大,也就是說存在過大的輸入元素,就需要對空間和時間進行考慮了
"
(labels ((maximum (arr max i)
(if (< i 0)
max
(maximum arr
(if (or (null max) (> (aref arr i) max))
(aref arr i)
max)
(1- i))))
(minimum (arr min i)
(if (< i 0)
min
(minimum arr
(if (or (null min) (< (aref arr i) min))
(aref arr i)
min)
(1- i))))
(fn-offset (num)
(if (< num 0)
(abs num)
(- 0 num))))
(let* ((len (length arr))
(offset (fn-offset (minimum arr nil (1- len))))
(k (+ 1 (+ (maximum arr nil (1- len)) offset)))
(arr-tmp (make-array `(,k) :initial-element 0))
(arr-out (make-array len)))
(dotimes (j len)
(setf (aref arr-tmp (+ offset (aref arr j))) (+ (aref arr-tmp (+ offset (aref arr j))) 1)))
(dotimes (i (1- k))
(setf (aref arr-tmp (+ i 1)) (+ (aref arr-tmp (+ i 1)) (aref arr-tmp i))))
(do ((j (1- len) (- j 1)))
((< j 0) arr-out)
(setf (aref arr-out (1- (aref arr-tmp (+ offset (aref arr j))))) (aref arr j))
(setf (aref arr-tmp (+ offset (aref arr j))) (- (aref arr-tmp (+ offset (aref arr j))) 1))))))
or
def `counting-sort` (a: Array[Int]): Array[Int] = {
/*
* 假設數據分布在[0 - 9]中,且數據個數不超過區間長度
*/
val min = 0
val max = 9
val c = new Array[Int](max - min + 1)
val b = new Array[Int](a.length)
for (i <- 0 until c.length) c(i) = 0
for (i <- 0 until a.length) c(a(i)) = c(a(i)) + 1
for (i <- 1 until c.length) c(i) = c(i) + c(i - 1)
for (i <- a.length - 1 to 0 by -1) {
b(c(a(i)) - 1) = a(i)
c(a(i)) = c(a(i)) - 1
}
b
}