选择排序
// 平均时间复杂度:O(n2)
// 最好情况:O(n2)
// 最坏情况:O(n2)
// 空间复杂度:O(1)
// 稳定性:稳定
func selectSort(arr: inout [Int]) {
let count = arr.count
for i in 0..<count {
var min = i
// 内层循环,每次找出未排序列表中最小的值
for j in i+1..<count {
if arr[j] < arr[min] {
min = j
}
}
if min != i {
arr.swapAt(min, i)
}
}
}
冒泡排序
// 平均时间复杂度:O(n2)
// 最好情况:O(n) 最坏情况:O(n2)
// 空间复杂度:O(1)
// 稳定性:稳定
func bubbleSort(arr: inout [Int]) {
for i in 0..<arr.count {
for j in 0..<arr.count - i - 1 {
if arr[j + 1] < arr[j] {
arr.swapAt(j + 1, j)
}
}
}
}
插入排序
插入排序是一种直观且简单的排序算法,核心思想是将元素逐个插入到已排序的序列中合适的位置,就像整理手中的扑克牌一样 —— 每次拿起一张牌,插入到前面已经排好序的牌中正确的位置。
-
优点:
- 简单易懂,实现方便。
- 对近乎有序的数组效率很高(时间复杂度接近
O(n))。 - 空间复杂度为
O(1)(原地排序,不需要额外空间)。 - 稳定排序(相等元素的相对顺序不变)。
-
缺点:
- 对大规模无序数组效率较低,时间复杂度为
O(n²)(最坏和平均情况)。 - 元素移动次数较多(每次插入可能需要移动多个元素)
- 对大规模无序数组效率较低,时间复杂度为
// 平均时间复杂度:O(n2)
// 最好情况:O(n) 最坏情况:O(n2)
// 空间复杂度:O(1)
// 稳定性:稳定
func insertSort(arr: inout [Int]) {
let count = arr.count
for i in 1..<count {
var j = i - 1
let key = arr[i] // 当前要排序的元素
// 将当前元素与已排序部分的元素从后向前比较
while j >= 0 && key < arr[j] {
arr[j + 1] = arr[j] // 元素后移
j -= 1
}
arr[j + 1] = key
}
}
快速排序
// 时间复杂度: 平均 O(NlogN),最坏 O(N2)(如果每次选择的枢轴都不理想)
// 空间复杂度: O(logN)(主要是递归栈开销)
// 原地排序: 不需要额外的数组空间
func quickSort(arr: inout [Int], low: Int, high: Int) {
guard !arr.isEmpty else {
return
}
if low >= high {
return
}
let part = getPart(arr: &arr, low: low, high: high)
quickSort(arr: &arr, low: 0, high: part - 1)
quickSort(arr: &arr, low: part + 1, high: high)
}
func getPart(arr: inout [Int], low: Int, high: Int) -> Int {
let pivot = arr[low]
var i = low + 1
var j = high
while i <= j {
//寻找大于pivot的元素,找到后交换
while i < high && arr[i] <= pivot {
i += 1
}
//寻找小于pivot的元素,找到后交换
while j > low && arr[j] > pivot {
j -= 1
}
if i >= j {
break
}
// 交换
arr.swapAt(i, j)
}
arr.swapAt(low, j)
return j
}
var arr = [4, 1, 7, 2, 5, 3, 6]
quickSort(arr: &arr, low: 0, high: arr.count - 1)