Swift In-place 排序(原地排序)
Swift In-place 排序(原地排序)

Swift In-place 排序(原地排序)

In-place 排序(原地排序)是一种排序算法,它直接在原数据结构上进行操作,而不需要额外的大量内存空间来存储中间数据或排序结果。原地排序的主要特点是空间复杂度较低,通常是  O(1) ,即只使用了少量的额外空间。

In-place 排序的特点

1、低空间复杂度:原地排序仅使用少量辅助变量或指针来操作数据,不需要额外的数组或容器。

2、原数据修改:排序直接对原始数据进行修改,排序结果会覆盖原来的数据。

3、高效的内存利用:在资源有限的情况下(如嵌入式系统),原地排序非常重要。

常见的 In-place 排序算法

以下是一些典型的 In-place 排序算法:

1、冒泡排序(Bubble Sort)

每次比较相邻的两个元素,并根据大小交换它们的位置。

空间复杂度: O(1)

时间复杂度: O(n2)

var array = [3, 1, 4, 1, 5, 9]
for i in 0..<array.count {
    for j in 0..<array.count - i - 1 {
        if array[j] > array[j + 1] {
            array.swapAt(j, j + 1)
        }
    }
}
print(array) // [1, 1, 3, 4, 5, 9]

2、插入排序(Insertion Sort)

逐步将元素插入到已排序的部分。

空间复杂度: O(1)

时间复杂度: O(n2)

var array = [3, 1, 4, 1, 5, 9]
for i in 1..<array.count {
    var j = i
    while j > 0 && array[j] < array[j - 1] {
        array.swapAt(j, j - 1)
        j -= 1
    }
}
print(array) // [1, 1, 3, 4, 5, 9]

3、选择排序(Selection Sort)

每次找到剩余未排序部分的最小值或最大值,并将其放到正确位置。

空间复杂度: O(1)

时间复杂度: O(n2)

var array = [3, 1, 4, 1, 5, 9]
for i in 0..<array.count {
    var minIndex = i
    for j in i+1..<array.count {
        if array[j] < array[minIndex] {
            minIndex = j
        }
    }
    array.swapAt(i, minIndex)
}
print(array) // [1, 1, 3, 4, 5, 9]

4、快速排序(Quick Sort)

使用分治法,通过选择一个基准值(pivot),将数组划分为两部分,然后递归排序两部分。

原地版本的快速排序对数组进行就地划分。

空间复杂度: O(log n) (递归栈)

时间复杂度: O(n log n)

func quickSort(_ array: inout [Int], low: Int, high: Int) {
    if low < high {
        let pivot = partition(&array, low: low, high: high)
        quickSort(&array, low: low, high: pivot - 1)
        quickSort(&array, low: pivot + 1, high: high)
    }
}

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

var array = [3, 1, 4, 1, 5, 9]
quickSort(&array, low: 0, high: array.count - 1)
print(array) // [1, 1, 3, 4, 5, 9]
快速排序的核心思想

快速排序通过以下步骤完成排序:

1、选择一个基准点(Pivot)

通常选择数组的最后一个元素(或其他策略),作为基准值。

2、分区(Partition)

将数组划分成两部分

左部分:所有小于基准值的元素。

右部分:所有大于等于基准值的元素。

此过程会确保基准值位于最终排序后的正确位置。

3、递归地排序两部分

分别对左部分和右部分递归应用快速排序,直到每部分长度为 1 或 0(此时已经有序)。

快速排序代码分析
func quickSort(_ array: inout [Int], low: Int, high: Int) {
    if low < high {
        let pivot = partition(&array, low: low, high: high) // 分区操作,获取基准点的索引
        quickSort(&array, low: low, high: pivot - 1)       // 递归排序左部分
        quickSort(&array, low: pivot + 1, high: high)     // 递归排序右部分
    }
}

quickSort 是一个递归函数。

它通过调用 partition 函数将数组分成两部分。

分区后的左、右部分分别通过递归调用 quickSort 进行排序。

func partition(_ array: inout [Int], low: Int, high: Int) -> Int {
    let pivot = array[high] // 基准点是最后一个元素
    var i = low             // i 指向分区的分界点
    
    for j in low..<high {
        if array[j] < pivot {       // 如果当前元素小于基准值
            array.swapAt(i, j)      // 交换小元素到分界点位置
            i += 1                  // 分界点右移
        }
    }
    array.swapAt(i, high)           // 将基准点放到分界点位置
    return i                        // 返回基准点的最终位置
}

核心作用是将数组重新排列,使得基准值的左侧全是比它小的元素,右侧全是比它大的元素。

返回值是基准值的索引,表示其最终的排序位置。

快速排序的意义

1、高效

平均时间复杂度为  O(n log n) 。

比如对于 100 万个元素的数组,分治法能迅速缩减规模。

2、灵活

可以适用于各种规模的数组。

3、原地排序

快速排序无需额外的内存(空间复杂度为  O(\log n) )。

不足之处

最坏情况下(如数组本身有序)时间复杂度可能退化为  O(n2) 。

不稳定排序:相同元素的顺序可能会被打乱。

非 In-place 排序的例子

对比原地排序,有一些排序算法需要额外的空间。

1、归并排序(Merge Sort)

需要创建额外的数组来存储中间结果,因此不是原地排序。

空间复杂度: O(n)

时间复杂度: O(n log n)

func mergeSort(_ array: [Int]) -> [Int] {
    guard array.count > 1 else { return array }
    let mid = array.count / 2
    let left = mergeSort(Array(array[..<mid]))
    let right = mergeSort(Array(array[mid...]))
    return merge(left, right)
}

func merge(_ left: [Int], _ right: [Int]) -> [Int] {
    var result: [Int] = []
    var i = 0, j = 0
    while i < left.count && j < right.count {
        if left[i] < right[j] {
            result.append(left[i])
            i += 1
        } else {
            result.append(right[j])
            j += 1
        }
    }
    result.append(contentsOf: left[i...])
    result.append(contentsOf: right[j...])
    return result
}

let array = [3, 1, 4, 1, 5, 9]
let sortedArray = mergeSort(array)
print(sortedArray) // [1, 1, 3, 4, 5, 9]

总结

In-place 排序 是直接修改原数据的排序方法,通常占用更少的内存。

常见的 In-place 排序算法包括冒泡排序、插入排序、选择排序和快速排序。

非 In-place 排序(如归并排序)虽然占用更多内存,但在某些情况下可能更适合需要稳定排序或更复杂场景的需求。

选择排序算法时,应根据数据规模和系统资源的限制来决定使用 In-place 还是非 In-place 排序方法。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注