《swift-algorithm-club》——算法/排序

805 阅读3分钟

排序

基础的排序(Basic sorts)

插入排序(Insertion Sort)

简单的原地排序实现

func insertionSort(_ array: [Int]) -> [Int] {
  var sortedArray = array
  for index in 1..<sortedArray.count {
    var currentIndex = index
    while currentIndex > 0 && sortedArray[currentIndex] < sortedArray[currentIndex - 1] {
      sortedArray.swapAt(currentIndex - 1, currentIndex)
      currentIndex -= 1
    }
  }
  return sortedArray
}

不交换的版本

func insertionSort(_ array: [Int]) -> [Int] {
  var sortedArray = array
  for index in 1..<sortedArray.count {
    var currentIndex = idnex
    let tmp = sortedArray[currentIndex]
    while currentIndex > 0 && temp < sortedArray[currentIndex - 1] {
      sortedArray[currentIndex] = sortedArray[currentIndex - 1]
      currentIndex -= 1
    }
    sortedArray[currentIndex] = temp
  }
  return sortedArray
}

泛型化

func ionsertionSort<T>(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] {
  var sortedArray = array
  for index in 1..<sortedArray.count {
    var currentIndex = idnex
    let tmp = sortedArray[currentIndex]
    while currentIndex > 0 && isOrderedBefore(temp, sortedArray[currentIndex - 1]) {
      sortedArray[currentIndex] = sortedArray[currentIndex - 1]
      currentIndex -= 1
    }
    sortedArray[currentIndex] = temp
  }
  return sortedArray
}
选择排序(Selection Sort)

找最小的值,和0索引处的元素交换位置;再在剩下的数组中找最小的,和索引1处的元素交换位置。。。

func selectionSort(_ array: [Int]) -> [Int] {
  guard array.count > 1 else { return array }
  
  var a = array
  
  for x in 0 ..< a.count - 1 {
    
    var lowest = x
    for y in x + 1 ..< a.count {
      if a[y] < a[lowest] {
        lowest = y
      }
    }
    
    if x != lowest {
      a.swapAt(x, lowest)
    }
  }
  return a
}
希尔排序(Shell Sort)

希尔排序是 将待排序的数组元素按下标的一定增量分组 ,分成多个子序列,然后对各个子序列进行直接插入排序算法排序;然后依次缩减增量再进行排序,直到增量为1时,进行最后一次直接插入排序,排序结束。

插入排序的一个高效改进版本

var arr = [64, 20, 50, 33, 72, 10, 23, -1, 4, 5]

public func shellSort(_ list: inout [Int]) {
  var sublistCount = list.count / 2
  while sublistCount > 0 {
    for pos in 0..<sublistCount {
      insertionSort(&list, start: pos, gap: sublistCount)
    }
    sublistCount = sublistCount / 2
  }
}

shellSort(&arr)

快速的排序(Fast sorts)

快速排序(Quicksort)

先来一个实现

func quicksort<T: Comparable>(_ a: [T]) -> [T] {
  guard a.count > 1 else { return a }
  
  let pivot = a[a.count / 2]
  let less = a.filter { $0 < pivot }
  let equal = a.filter { $0 == pivot }
  let greater = a.filter { $0 > pivot }
  
  return quicksort(less) + equal + quicksort(greater)
}

用了三次filter,不太高效,换成Lomuto的分区方案

func partitionLomuto<T: Comparable>(_ a: inout [T], low: Int, high: Int) -> Int {
  let pivot = a[high]
  
  var i = low
  for j in low..<high {
    if a[j] <= pivot {
      a.swapAt(i, j)
      i += 1
    }
  }
  a.swapAt(i, high)
  return i
}

for 循环中,将数组分为4个区域:

  1. a[low...i] 包含所有<= pivot 的值
  2. a[i+1...j-1] 包含所有 > pivot 的值
  3. a[j...hign-1] 是我们还没有查看的部分
  4. a[high] 是基准值 pivot
func quicksortLomuto<T: Comparable>(_ a: inout [T], low: Int. high: Int) {
  if low < high {
    let p = partionLomuto(&a, low: low, high: high)
    quicksortLomuto(&a, low: low, hign: p - 1)
    quicksortLomuto(&a, low: p + 1, high: high)
  }
}

Hoare的分区方案

func partitionHoare<T: Comparable>(_ a: inout [T], low: Int, hign: Int) -> Int {
  let pivot = a[low]
  var i = low - 1
  var j = high + 1
  
  while true {
    repeat { j -= 1 } while a[j] > pivot
    repeat { i += 1 } while a[i] < pivot
    
    if i < j {
      a.swapAt(i, j)
    } else {
      return j
    }
  }
}

func quicksortHoare<T: Comparable>(_ a: inout [T], low: Int, high: Int) {
  if low < high {
    let p = partitionHoare(&a, low: low, high: high)
    quicksortHoare(&a, low: low, high: p)
    quicksortHoare(&a, low: p + 1, high: high)
  }
}

选择一个好的基准,理想情况下,中位数是最好的,但是不排序我们就不知道中位数。。。

一种方案是随机选择基准,平均而言,这会有一个好的结果

func quicksortRandom<T: Comparable>(_ a: input [T], low: Int, high: Int) {
  if low < high {
    let pivotIndex = random(main: low, max: high)
    
    (a[pivotIndex], a[high]) = (a[high], a[pivotIndex])
    
    let p = partitionLomuto(&a, low: low, high: high)
    quicksortRandom(&a, low: low, high: p - 1)
    quicksortRandom(&a, low: p + 1, high: high)
  }
}

荷兰国旗分区

func partitionDutchFlag<T: Comparable>(_ a: inout [T], low: Int, high: Int, pivotIndex: Int) -> (Int, Int) {
  let pivot = a[pivotIndex]
  
  var smaller = low
  var equal = low
  var larger = high
  
  while equal <= larger {
    if a[equal] < pivot {
      swap(&a, samller, equal)
      smaller += 1
      equal += 1
    } else if a[equal] == pivot {
      equal += 1
    } else {
      swap(&a, equal, larger)
      larger -= 1
    }
  }
  return (smaller, larger)
}

这和 Lomuto 方案的工作方式非常相似,只是循环将数组分为4个区域

  • [low ... smaller-1] 包含所有 < pivot的值
  • [smaller ... equal-1] 包含所有 == pivot的值
  • [equal ... larger] 包含所有 > pivot 的值
  • [larger ... high] 是我们还没查看的值
func quicksortDutchFlag<T: Comparable>(_ a: inout [T], low: Int,high: Int) {
  if low < high {
    let pivotIndex = random(min: low, max: high)
    let (p, q) = partitionDutchFlag(&a, low: low, high: high, pivotIndex: pivotIndex)
    quicksortDutchFlag(&a, low: low, high: p - 1)
    quicksortDutchFlag(&a, low: q + 1, high: high)
  }
}
归并排序(Merge Sort)

归并排序算法使用 分而治之 的方法。归并排序算法可以分为 先差分后合并

自上而下的实现(递归)

func mergeSort(_ array: [Int]) -> [Int] {
  guard array.count > 1 else { return array }
  
  let middleIndex = array.count / 2
  
  let leftArray = mergeSort(Array(array[0..<middleIndex]))
  
  let rightArray = mergeSort(Array(array[middleIndex..<array.count]))
  
  return merge(leftPile: leftArray, rightPile: rightArray)
}
func merge(leftPile: [Int], rightpile: [Int]) -> [Int] {
  var leftIndex = 0
  var rightIndex = 0
  
  var orderedPile = [Int]()
  orderedPile.reserveCapacity(leftPile.count + rightPile.count)
  
  while leftIndex < leftPile.count && rightIndex < rightpile.count {
    if leftPile[leftIndex] < rightPile[rightIndex] {
      orderedPile.append(leftPile[leftIndex])
      leftIndex += 1
    } else if leftPile[leftIndex] > rightPile[rightIndex] {
      orderedPile.append(rightPile[rightIndex])
      rightIndex += 1
    } else {
      orderedPile.append(leftPile[leftIndex])
      leftIndex += 1
      orderedPile.append(rightPile[rightIndex])
      rightIndex += 1
    }
  }
  
  while leftIndex < leftPile.count {
    orderedPile.append(leftPile[leftIndex])
    leftIndex += 1
  }
  
  while rightIndex < rightPile.count {
    orderedPile.append(rightPile[rightIndex])
    rightIndex += 1
  }
  
  return orderedPile
}

自下而上的实现(迭代)

func mergeSortBottomUp<T>(_ a: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] {
	let n = a.count
  
  var z = [a, a]
  var d = 0
  
  var width = 1
  while width < n {
    
    var i = 0
    while i < n {
      
      var j = i
      var l = i
      var r = i + width
      
      let lmax = min(l + width, n)
      let rmax = min(r + width, n)
      
      while l < lmax && r < rmax {
        if isOrderedBefore(z[d][l], z[d][r]) {
          z[1 - d][j] = z[d][l]
          l += 1
        } else {
          z[1 - d][j] = z[d][r]
          r += 1
        }
        j += 1
      }
      while l < lmax {
        z[1 - d][j] = z[d][l]
        j += 1
        l += 1
      }
      while r < rmax {
        z[1 - d][j] = z[d][r]
        j += 1
        r += 1
      }
      
      i += width*2
    }
    
    width *= 2
    d = 1 - d
  }
  return z[d]
}

归并排序不是原地排序

堆排序(Heap Sort)

堆排序性能最好、最差和平均情况下都是 O(n lg n)。原地排序,不稳定

extension Heap {
  public mutating func sort() -> [T] {
    for i in stride(from: (elements.count - 1), through: 1, by: -1) {
      swap(&elements[0], &elements[i])
      shiftDown(0, heapSize: i)
    }
    return elements
  }
}

注意,从低到高排序需要一个最大堆,想进行 < 排序,必须使用 > 作为sort函数创建堆。

var h1 = Heap(array: [5, 13, 2, 25, 7, 17, 20, 8, 4], sort: >)
let a1 = h1.sort()

混合的排序(Hybrid sorts)

内省排序(Introsort)

内省排序首先从快速排序开始,当递归深度达到某个最大值后转为堆排序,如果分区的计数在20以内,则使用插入排序。

伪代码实现

procedure sort(A : array):
    let maxdepth = ⌊log(length(A))⌋ × 2
    introSort(A, maxdepth)

procedure introsort(A, maxdepth):
    n ← length(A)
    if n < 20:
        insertionsort(A)
    else if maxdepth = 0:
        heapsort(A)
    else:
        p ← partition(A)  // the pivot is selected using median of 3
        introsort(A[0:p], maxdepth - 1)
        introsort(A[p+1:n], maxdepth - 1)

特殊的排序(Special-purpose sorts)

计数排序(Counting Sort)

举个栗子

[10, 9, 8, 7, 1, 2, 7, 3 ]

第一步:

计算数组中每个项的总出现次数

Index 0 1 2 3 4 5 6 7 8 9 10
Count 0 1 1 1 0 0 0 2 1 1 1
let maxElement = array.max() ?? 0

var countArray = [Int](repeating: 0, count: Int(maxElement + 1))
for element in array {
  countArray[element] += 1
}

第二步:

算法尝试确定每个在每个元素之前放置的元素个数

Index 0 1 2 3 4 5 6 7 8 9 10
count 0 1 2 3 3 3 3 5 6 7 8
for index in 1 ..< countArray.count {
  let sum = countArray[index] + countArray[index - 1]
  countArray[index] = sum
}

第三步:

原始数组中的每个元素都放置在第二步的输出定义的位置。

Index  0 1 2 3 4 5 6 7
Output 1 2 3 7 7 8 9 10
var sortedArray = [Int](repeating: 0, count: array.count)
for index in stride(from: array.count - 1, through: 0, by: -1) {
  let elemnet = array[index]
  countArray[element] -= 1
  sortedArray[countArray[element]] = element
}
return sortedArray
基数排序(Radix Sort)

举个例子

初始状态  按个位排序  按十位排序  按百位排序
170				170       802        002
090       090       002        024
802       802       024        045
002       002       045        066
024       024       066        075
045       045       170        090
075       075       075        170
066       066       090        802
拓扑排序(Topological Sort)

拓扑排序是一种对有向图的排序算法,使得对于每个有向边 u->v,顶点 uv 之前

第一步:找到所有入度为0的顶点

第二步:通过深度优先搜索遍历图

第三步:记住所有访问过的顶点

第四步:全部放在一起

extension Graph {
  public func topologicalSort() -> [Node] {
    
    let startNodes = calculateInDegreeOfNodes().filter({ _, indegree in
      return indegree == 0
    }).map({ node, indegree in
    	return node
    })
    
    var visited = [Node : Bool]()
    for (node, _) in adjacencyLists {
      visited[node] = false
    }
    
    var result = [Node]()
    for startNode in startNodes {
      result = depthFirstSearch(startNode, visited: &visited) + result
    }
    
    return result
  }
}

不好的排序算法(知道就行,别用!)(Bad sorting algorithms (don't use these!))

冒泡排序(Bubble Sort)
for i in 0..<array.count {
  for j in 1..<array.count - i {
    if array[j] < array[j-1] {
      let tmp = array[j-1]
      array[j-1] = array[i]
      array[j] = tmp
    }
  }
}
return array
慢排序(Slow Sort)

将问题分为2步:

  1. 找到最大的元素
  2. 将剩余的元素排序

然后第一步又可以分为

  1. 找到前 n/2 的最大值
  2. 找到剩余 n/2 的最大值
  3. 选出这2者的最大值
func slowSort(_ i: Int, _ j: Int, _ numberList: inout [Int]) {
  guard if i < j else { return }
  let m = (i + j)/2
  slowSort(i, m, &numberList)
  slowSort(m+1, j, &numberList)
  if numberList[j] < numberList[m] {
    let temp = numberList[m]
    numberList[j] = numberList[m]
    numberList[m] = temp
  }
  slowSort(i, j-1, &numberList)
}