在上一篇笔记《顺序表的查找》中,我们用到了有序表,所以这里简单的搞一下有序表——对任意给定的列表按照某种顺序进行排序,使其成为一个新的有序列表。
对列表进行排序的方法有很多,这里只简单的介绍一下插入排序(Insertion Sort)、冒泡儿排序(Bubble Sort)、选择排序(Selection Sort),以及快速排序(Quick Sort)。
一、插入排序
插入排序(Insertion Sort)
是最简单的排序,其基本操作是,将一个元素插入到一个已经排序好了的有序列表中,从而得到一个新的、元素加1的有序列表。具体来说,就是先假设第一个数据是已经排序好了的,然后再用第二个数据和第一个数据进行对比,如果发现它比第一个数据小,就将它插入到第一个数据的前面,如果它比第一个数据大,就保持原来的位置不变;接着,再用第三个数据去和前面已经排好序的两个数据进行比较,然后将其插入到合适的位置,依此类推。
插入排序
的基本思想是不变的,但是实际应用可能有所不同。比如说,假设给定的数组numberList是不可变的,那么我们需要借助一个可变的临时数组tmpArr,先将numberList赋值给tmpArr,然后再对tmpArr执行排序。用代码表示为:
// 给定一个无序的整数列表
let numberList = [49, 38, 65, 97, 76, 13, 27, 49]
extension Array where Element: Comparable {
/// 排序完成以后返回一个新的有序列表
func insertionSort() -> Array {
// 如果数组中的元素只有1个,就没必要进行排序了
guard self.count > 1 else { return self }
// 容器数组,用于对数组中的元素进行排序操作
var tmpArr = self
// 遍历数组tmpArr的下标
for i in 0.. -1 {
// 如果元素key小于元素tmpArr[j]
if key < tmpArr[j] {
// for循环第1趟,while循环第1趟,key = tmpArr[i] = tmpArr[0] = 49,tmpArr[j] = tmpArr[0] = 49,所以不会执行if中的代码
// for循环第2趟,while循环第2趟,key = tmpArr[i] = tmpArr[1] = 38,tmpArr[j] = tmpArr[1] = 38,所以不会执行if中的代码
// for循环第2趟,while循环第3趟,key = tmpArr[i] = tmpArr[1] = 38,tmpArr[j] = tmpArr[0] = 49,所以会执行if中的代码
// 删除数组中指定的元素
tmpArr.remove(at: j + 1)
// while循环执行到第3趟之后,此时j = 0,而j + 1 = 1,所以tmpArr.remove(at: j + 1)会删除元素38
// 在指定位置插入一个元素
tmpArr.insert(key, at: j)
// while循环执行到第3趟之后,此时key = 38,j = 0,所以tmpArr.insert(key, at: j)表示将元素38插入到0所在位置
// 插入完成之后,元素49之前所在的下标为0,现在变为1了,而元素38之前的下标为1,现在变为0了,此时tmpArr排序为:
// [38, 49, 65, 97, 76, 13, 27, 49]
}
// 排序完成,退出while循环
j -= 1
// for循环第1趟,while循环第1趟过后,j = -1,则退出while循环,再次进入for循环
// for循环第2趟,while循环第2趟过后,j = 0,它是大于-1的,所以还会在执行一次while循环
// for循环第2趟,while循环第3趟过后,j = -1,则退出while循环,再次进入for循环
}
}
// 返回排序好了的数组
return tmpArr
}
}
// 执行排序
let results = numberList.insertionSort() // 结果为:[13, 27, 38, 49, 49, 65, 76, 97]
另外一种情况,假设给定的数组numberList本身就是可变的,那么就无需借助临时数组了,可以直接对其执行排序操作:
// 给定一个无序的整数列表
var numberList = [49, 38, 65, 97, 76, 13, 27, 49]
/// 插入排序
/// - 参数list:表示可变数组的引用
public func insertionSort(_ list: inout [T] ) {
// 如果只有一个元素,则不需要执行排序
if list.count <= 1 {
return
}
// 取出除第0个元素之外所有元素的下标
for i in 1.. 0 && list[j - 1] > x {
// 用元素list[j - 1]覆盖元素list[j]
list[j] = list[j - 1]
// 下标j的值减1
j -= 1
}
// 将元素x覆盖元素list[j](此时j的值变小了一位,因此相当于元素x的位置向前移动了一位)
list[j] = x
}
}
// 对无序数组numberList执行排序
insertionSort(&numberList) // [13, 27, 38, 49, 49, 65, 76, 97]
二、冒泡儿排序
冒泡儿排序(Bubble Sort)
的核心思想就是交换,其具体执行过程为,先将第一个元素和第二个元素进行比较,如果为逆序(第一个元素比第二个元素大),则交换第一个元素和第二个元素的位置,接着比较第二个元素和第三个元素,如果仍然为逆序,则交换第二个元素和第三个元素的位置,以此类推直至第一趟冒泡儿完成,此时序列中最大的元素置于尾部。紧接着按照上述步骤执行第二趟、第三趟....直到序列中所有元素都按照顺序进行排列为止。用代码表示为:
// 给定一个无序数组列表
let numberList = [49, 38, 65, 97, 76, 13, 27, 49]
extension Array where Element: Comparable {
/// 对任意给定的数组进行排序,然后返回一个排序好的数组
func bubbleSort() -> Array {
// 数组中元素个数大于1时才有意义
guard self.count > 1 else { return self }
// 临时数组,用于对数组中的元素进行排序操作
var tmpArr = self
// 取出数组中所有元素的下标值,从前往后遍历
for i in 0.. tmpArr[j + 1]) {
/// 调用swapAt(_:_:)函数,交换集合中指定索引位置的元素值
///
/// 这两个参数必须是集合的有效索引,因此都不能等于endIndex。
/// 另外,如果参数i和j的值相等,那么调用swapAt(_:_:)函数则
/// 没有任何意义
///
/// - 函数参数:
/// - i: 要交换的第一个值的索引
/// - j: 要交换的第二个值的索引
tmpArr.swapAt(j, j + 1) // swap(&tmpArr[j], &tmpArr[j + 1])
}
}
}
// 返回排序好的数组
return tmpArr
}
}
// 执行排序
let results = numberList.bubbleSort() // [13, 27, 38, 49, 49, 65, 76, 97]
三、选择排序
选择排序(Selection Sort)
的核心思想是,每一趟排序都在n-i+1(其中,i = 1, 2, ..., n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。说得直白点,每一趟的选择排序就是从序列中未排好顺序的元素中选择一个最小的元素,然后将该元素与这些未排好序的元素的第一个元素交换位置。用代码表示为:
let numberList = [49, 38, 65, 97, 76, 13, 27, 49]
extension Array where Element: Comparable {
/// 对一个无序序列进行排序,然后返回一个
/// 已经排好顺序的数组
func selectionSort() -> Array {
guard self.count > 1 else { return self }
var tmpArr = self
for i in 0.. tmpArr[j] {
minimum = j
}
j += 1
}
// 交换最小值
if i != minimum {
tmpArr.swapAt(i, minimum) // swap(&tmpArr[i], &tmpArr[minimum])
}
}
return tmpArr
}
}
// 执行排序
let results: Array = numberList.selectionSort() // [13, 27, 38, 49, 49, 65, 76, 97]
四、快速排序
快速排序(Quick Sort)
是对冒泡儿排序的一种改进,其基本思想是,在当前排序序列中任意选择一个元素作为支点(pivot),把小于或等于支点的所有元素都移动到支点的前面,把大于支点的所有元素都移动到支点后面,这样一来,以支点为分界线,将整个序列分割成前后两个子序列,并且支点刚好就处于整个排序过程的最终位置。接下来,对支点前后两个子序列重复执行上述操作,直至整个序列变得有序。用代码表示为:
var numberList = [49, 38, 65, 97, 76, 13, 27, 49]
extension Array where Element: Comparable {
/// 对无序列表执行快速排序
mutating func quickSort() -> Array {
/// 对序列进行排序
/// - 参数start:表示序列的起始位置
/// - 参数end:表示序列的末尾位置
func qSort(_ start: Int, _ end: Int) {
if (start < end) {
// 确立支点,对序列继续进行分割
let iPivot = qPartition(start, end)
// 递归调用qSort(_ : , _ : )函数,继续对序列进行分割
qSort(start, iPivot - 1) // 所有比支点小的
qSort(iPivot + 1, end) } // 所有比支点大的
}
// 调用qSort(_ : , _ : )函数,传入数组的开始索引和尾部索引
// - startIndex: 表示非空数组中第一个元素的位置
// - endIndex: 表示非空数组中最后一个元素的下一个位置
// - 如果数组为空,startIndex和endIndex相等
qSort(self.startIndex, self.endIndex - 1)
// 返回排序完成的序列
return self
}
/// 对序列进行分割(确立支点)
/// - 参数startIndex:
/// - 参数endIndex:
/// - returns:
mutating func qPartition(_ start: Int, _ end: Int) -> Int {
// 声明一个支点,并对其初始化
var pivotIndex = start // 用子表的第一个元素作为支点
for currentIndex in pivotIndex..
五、总结
插入排序
、冒泡儿排序
和选择排序
的时间复杂度均为O( n²),而快速排序
的时间复杂度是O(logn)。因此,相比而言,快速排序
的效率更高。事实上,在实际开发过程中,前面三种排序使用的比较少,而快速排序
在解决一般问题时则拥有大量的应用,应该掌握。