快速排序算法

119 阅读3分钟

一、快速排序的核心思想是通过不断地将集合分割成较小的子集,然后对这些子集进行排序,最终合并它们以得到整体有序的集合。它采用了以下关键步骤:

  1. 选择基准元素:从集合中选择一个元素作为基准(通常是集合中的某个元素,如第一个、最后一个或中间一个元素)。

  2. 划分:将集合中的元素根据与基准元素的比较结果,分成两个子集:

    • 一个子集包含所有小于基准的元素。
    • 另一个子集包含所有大于基准的元素。
  3. 递归排序:递归地对划分出来的两个子集重复上述两个步骤,直到子集的大小为 1 或 0。这时,它们被视为已排序。

  4. 合并:将所有已排序的子集合并起来,形成最终的有序集合。

快速排序的关键在于划分步骤,它使得集合中的元素按照大小分散到两个子集中,从而实现了排序。这个过程重复进行,直到整个集合变得有序。

快速排序的特点包括:

  • 高效性:它通常比其他排序算法快,特别是对于大型数据集。
  • 原地排序:它可以在原始数据集内部进行排序,而不需要额外的空间。
  • 不稳定性:它在排序相等元素时可能会改变它们的相对顺序。

总之,快速排序的核心思想是使用分治策略,通过选择基准元素和不断划分集合来实现高效的排序。

二、下面是一个使用 Swift 实现的快速排序算法的示例:

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

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

// 使用示例
var numbers = [5, 2, 8, 1, 3]
quickSort(&numbers, low: 0, high: numbers.count - 1)
print(numbers) // 输出 [1, 2, 3, 5, 8]

上述代码解释说明:

  1. quickSort: 这是快速排序的主函数。它接受一个可变的数组 array、一个起始索引 low 和一个结束索引 high。在这个函数中,它首先检查是否有元素需要排序(low < high),然后调用 partition 函数来划分数组,然后递归地对划分出的两个子数组进行排序。

  2. partition: 这个函数用于选择一个基准元素(通常是最后一个元素),并将数组划分为两部分:左边的部分包含小于或等于基准的元素,右边的部分包含大于基准的元素。它返回基准元素的最终位置。

  3. swapAt 是 Swift 标准库提供的一个数组操作方法,用于交换数组中两个元素的位置。具体地说,它会将指定索引位置的两个元素进行交换。

    • 例如,如果 i 对应的元素是小于基准的元素,j 对应的元素是大于基准的元素,那么这行代码将它们的位置交换,以实现集合的分割。交换后,小于基准的元素位于基准的左侧,大于基准的元素位于右侧,这是快速排序算法中划分步骤的关键操作之一。
  4. T: Comparable 是 Swift 中的一个泛型约束(generic constraint)的表达方式,它指定了泛型类型 T 必须是实现了 Comparable 协议的类型。这个约束的意思是,类型 T 必须支持比较操作,以便在排序等算法中进行元素的比较。

5.Comparable 协议是 Swift 标准库中的一个协议,用于定义可比较的类型。这个协议要求实现以下两个方法:

  • static func < (lhs: Self, rhs: Self) -> Bool: 用于定义小于操作符,判断一个值是否小于另一个值。
  • static func == (lhs: Self, rhs: Self) -> Bool: 用于定义等于操作符,判断两个值是否相等。

通过实现这两个方法,类型可以表达出它们是可比较的。

当你在泛型函数或泛型类型中使用 <T: Comparable> 这样的约束时,它表示你的函数或类型可以接受任何实现了 Comparable 协议的类型 T 作为参数或泛型参数。这使得你可以在函数内部使用 <== 操作符来进行元素的比较,而不用担心 T 是否支持这些操作。

例如,在快速排序的实现中,要求泛型类型 T 必须是可比较的,因为排序需要比较元素的大小来进行划分和排序操作。所以,使用 T: Comparable 约束确保了排序算法能够正确地比较和排序不同类型的元素。

然后,使用示例对一个整数数组进行快速排序。这个示例中,数组 numbers 在排序后变得有序。 请注意,这个实现是基于递归的,它将数组不断划分为较小的子数组,直到子数组的大小为 1 或 0,因此是一个典型的快速排序算法。