排序是我们的日常开发中经常会遇到的需求,例如,在商品的列表页面,我们可以根据各种维度(销量、价格、人气等)对商品的展示顺序进行改变。

所以,对各个排序的性能的了解也是基础且重要的。我们先对排序这一块进行一个整体的把握。

  • 冒泡排序
  • 选择排序
  • 插入排序
  • 希尔排序
  • 堆排序
  • 归并排序
  • 快速排序

关于排序的具体过程,本文没有利用图示来具体说明,但是在代码中对每一步执行加上了一些 log,有兴趣的可以复制代码到自己的 Playgroud 中运行查看。

冒泡排序

基本思想

两两比较相邻记录,如果反序则交换,直到没有反序的记录为止。

代码实现

func bubbleSort(_ items: [Int]) -> [Int] {var arr = itemsfor i in 0..<arr.count {print("第\(i + 1)轮冒泡:")var j = arr.count - 1while j > i {if arr[j-1] > arr[j] {print("\(arr[j-1]) > \(arr[j]): 进行交换")arr.swapAt(j-1, j)} else {print("\(arr[j-1]) <= \(arr[j]): 不进行交换")}j -= 1}print("第\(i + 1)轮冒泡结束")print("当前结果为:\n\(arr)\n")}return arr
}
复制代码

根据上面的实现过程,可以发现如果数组是类似于 [2, 1, 3, 4, 5] 的话,两次冒泡之后,数组就已经调整为 [1, 2, 3, 4, 5]。此时,再执行后面的循环其实是多余了,所以我们在发现冒泡循环没有进行交换的话,说明排序就已经完成了。下面我们可以将代码优化一下:

func bubbleSort_v2(_ items: [Int]) -> [Int] {var arr = itemsvar flag = truefor i in 0..<arr.count {print("第\(i + 1)轮冒泡:")if !flag {return []}flag = falsevar j = arr.count - 1while j > i {if arr[j-1] > arr[j] {print("\(arr[j-1]) > \(arr[j]): 进行交换")arr.swapAt(j-1, j)flag = true} else {print("\(arr[j-1]) <= \(arr[j]): 不进行交换")}j -= 1}print("第\(i + 1)轮冒泡结束")print("当前结果为:\n\(arr)\n")}return arr
}
复制代码

选择排序

基本思想

每一次从待排序的数据元素中选择最小(或最大)的一个元素放在已排好序列的末尾,直到所有元素排完为止。

代码实现

func selectSort(_ items: [Int]) -> [Int] {var arr = itemsfor i in 0..<arr.count {print("第\(i+1)轮选择,x选择下标的范围为\(i)----\(arr.count)")var j = i + 1var minValue = arr[i]var minIndex = i// 寻找无序部分中最小值while j < arr.count {if minValue > arr[j] {minValue = arr[j]minIndex = j}j = j + 1}print("在后半部分乱序数列中,最小值为:\(minValue),下标为:\(minIndex)")// 与无序表中的第一个值交换,让其成为有序表中的最后一个值if minIndex != i {arr.swapAt(minIndex, i)}print("本轮结果为:\(arr)\n")}return arr
}
复制代码

插入排序

基本思想

每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止,最终可以得到一个有序表。

代码实现

func insertSort(_ items: [Int]) -> [Int] {var arr = itemsfor i in 0..<arr.count {print("第\(i)轮插入:")print("要选择插入的值为:\(arr[i])")var j = iwhile j > 0 {if arr[j] < arr[j-1] {arr.swapAt(j, j-1)j -= 1} else {break}}print("插入的位置:\(j)")print("本轮插入完毕,插入结果为:\n\(arr)\n")}return arr
}
复制代码

希尔排序

基本思想

其实希尔排序是插入排序的升级版,希尔排序根据其排序的特点又叫做缩小增量排序。希尔排序的大体步骤就是先将无序序列按照一定的步长(增量)分为几组,分别将这几组中的数据通过插入排序的方式将其进行排序。然后缩小步长(增量)分组,然后将组内的数据再次进行排序。直到增量为 1 位置。经过上述这些步骤,我们的序列就是有序的了。其实上述的插入排序就是增量为 1 的希尔排序。

代码实现

func hillSort(_ items: [Int]) -> [Int] {var arr = itemsvar step: Int = arr.count / 2while step > 0 {print("步长为\(step)的插入排序开始:")for i in 0..<arr.count {var j = i + stepwhile j >= step && j < arr.count {if arr[j] < arr[j-step] {arr.swapAt(j, j-step)j = j - step} else {break}}}print("步长为\(step)的插入排序结束")print("本轮排序结果为:\(arr)\n")step = step / 2      // 缩小步长}return arr
}
复制代码

堆排序

准备知识

是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子。

大顶堆: arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

小顶堆: arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

基本思想

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序序列了。

基本步骤

  1. 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆。
  2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端。
  3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整及交换步骤,直到整个序列有序。

代码实现

// 构建小顶堆的层次遍历序列
func heapCreate(items: inout Array<Int>) {var i = items.countwhile i > 0 {heapAdjast(items: &items, startIndex: i - 1, endIndex: items.count)i -= 1}
}// 对小顶堆j的局部进行调整, 使其该节点的所有父类符合小顶堆的特点
func heapAdjast(items: inout Array<Int>, startIndex: Int, endIndex: Int) {let temp = items[startIndex]var fatherIndex = startIndex + 1    //父节点下标var maxChildIndex = 2 * fatherIndex //左孩子下标while maxChildIndex <= endIndex {if maxChildIndex < endIndex && items[maxChildIndex-1] < items[maxChildIndex] {maxChildIndex = maxChildIndex + 1}if temp < items[maxChildIndex-1] {items[fatherIndex-1] = items[maxChildIndex-1]} else {break}fatherIndex = maxChildIndexmaxChildIndex = 2 * fatherIndex}items[fatherIndex-1] = temp
}func heapSort(_ items: [Int]) -> [Int] {var arr = itemsvar endIndex = arr.count - 1print("原始堆")print(arr)// 创建小顶堆,其实就是讲arr转换成小顶堆层次的遍历结果heapCreate(items: &arr)print(arr)print("堆排序开始")while endIndex > 0 {// 将小顶堆的定点与小顶堆的最后一个值进行交换arr.swapAt(endIndex, 0)endIndex -= 1    // 缩小小顶堆的范围// 对交换后的小顶堆进行调整,使其重新成为小顶堆print("重新调整")heapAdjast(items: &arr, startIndex: 0, endIndex: endIndex + 1)print("调整后的结果如下")print(arr)}return arr
}
复制代码

归并排序

基本思想

归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略。

将需要排序的数组分解为一个个单独的子元素,两两比较后将其合并为一个有序数组,不断重复比较及合并的过程,直至合并之后的有序数组的元素数量与原数组相同时,则排序结束。

代码实现

// 合并两个数组
func mergeArray(firstList: Array<Int>, secondList: Array<Int>) -> Array<Int> {var resultList: Array<Int> = []var firstIndex = 0var secondIndex = 0// 拿出两数组第一个元素,比较之后取小的插入新数组while firstIndex < firstList.count && secondIndex < secondList.count {if firstList[firstIndex] < secondList[secondIndex] {resultList.append(firstList[firstIndex])firstIndex += 1} else {resultList.append(secondList[secondIndex])secondIndex += 1}}// 将第一个数组剩下的元素插入新数组while firstIndex < firstList.count {resultList.append(firstList[firstIndex])firstIndex += 1}// 将第二个数组剩下的元素插入新数组while secondIndex < secondList.count {resultList.append(secondList[secondIndex])secondIndex += 1}return resultList
}func mergeSort(_ items: [Int]) -> [Int] {let arr = items// 将数组中的每一个元素放入一个数组中var tempArray: Array<Array<Int>> = []for item in arr {var subArray: Array<Int> = []subArray.append(item)tempArray.append(subArray)}// 对这个数组中的数组进行合并,直到合并完毕为止while tempArray.count != 1 {print(tempArray)var i = 0while i < tempArray.count - 1 {print("将\(tempArray[i])与\(tempArray[i+1])合并")tempArray[i] = mergeArray(firstList: tempArray[i], secondList: tempArray[i+1])print("合并结果为:\(tempArray[i])\n")tempArray.remove(at: i + 1)i = i + 1}}return tempArray.first!
}
复制代码

快速排序

基本思想

快速排序(Quick Sort)使用分治法策略。

选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序流程:

  1. 从数列中挑出一个基准值。
  2. 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
  3. 递归地把“基准值前面的子数列”和“基准值后面的子数列”进行排序。

代码实现

/// 将数组以第一个值为准分为两部分,前半部分比该值要小,后半部分比该值要大
///
/// - parameter list: 要二分的数组
/// - parameter low:  数组的下界
/// - parameter high: 数组的上界
///
/// - return: 分界点
func partition(list: inout Array<Int>, low: Int, high: Int) -> Int {var low = lowvar high = highlet temp = list[low]while low < high {while low < high && list[high] >= temp {high -= 1}list[low] = list[high]while low < high && list[low] <= temp {low += 1}list[high] = list[low]print("====low==\(low)======high==\(high)")}list[low] = tempprint("=====temp==\(temp)")print(list)return low
}/// 快速排序
///
/// - parameter list: 要排序的数组
/// - parameter low:  数组的下界
/// - parameter high: 数组的上界
func quickSortHelper(list: inout Array<Int>, low: Int, high: Int) {if low < high {let mid = partition(list: &list, low: low, high: high)quickSortHelper(list: &list, low: low, high: mid - 1)quickSortHelper(list: &list, low: mid + 1, high: high)}
}func quickSort(_ items: Array<Int>) -> Array<Int> {var list = itemsquickSortHelper(list: &list, low: 0, high: list.count - 1)print("快速排序结束")return list
}
复制代码

Swift排序用法

在Swift源代码中,sort 函数采用的是一种内审算法(IntroSort)。它由堆排序、插入排序、快速排序三种算法构成,依据输入的深度相应选择最佳的算法来完成。

// 以升序排列为例,array 数组可改变
array.sort// 以降序排列为例,array 数组不可改变
newArray = array.sorted(by: >)
复制代码

总结

排序方法 时间复杂度 辅助空间
冒泡排序 O(n²) O(1)
选择排序 O(n²) O(1)
插入排序 O(n²) O(1)
希尔排序 与增量选取策略有关 O(1)
堆排序 O(nlogn) O(1)
归并排序 O(nlogn) O(n)
快速排序 O(nlogn) O(logn)-O(n)

转载于:https://juejin.im/post/5cf4955df265da1b7401e6da

算法学习:常用排序方法相关推荐

  1. C语言常用排序方法大全

    C语言常用排序方法大全 /* ============================================================================= 相关知识介绍( ...

  2. Java黑皮书课后题第7章:**7.18(冒泡排序)使用冒泡排序算法编写一个排序方法。编写一个测试程序,读取10个double型的值,调用这个方法,然后显示排序好的数字

    **7.18(冒泡排序)使用冒泡排序算法编写一个排序方法.编写一个测试程序,读取10个double型的值,调用这个方法,然后显示排序好的数字 题目 题目描述 冒泡排序法 / 下沉排序法 破题 代码 运 ...

  3. python怎么降维_【Python算法】常用降维方法-常用降维方法解读

    常用降维方法-常用降维方法解读 1. 引言 机器学习领域中所谓的降维就是指采用某种映射方法,将原高维空间中的数据点映射到低维度的空间中.降维的本质是学习一个映射函数 f : x->y,其中x是原 ...

  4. c语言的5种常用排序方法

    c语言的5种常用排序算法 1.冒泡排序(最常用) 冒泡排序是最简单的排序方法:原理是:从左到右,相邻元素进行比较.每次比较一轮,就会找到序列中最大的一个或最小的一个.这个数就会从序列的最右边冒出来.( ...

  5. python怎么降维_【Python算法】常用降维方法-常用降维方法的目的

    常用降维方法-常用降维方法的目的 正所谓每一个结果的出现都是一系列的原因导致的,当构建机器学习模型时候,有时候数据特征异常复杂,这就需要经常用到数据降维技术,下面主要介绍一些降维的主要原理. 1. 降 ...

  6. 快速排序算法_常用排序算法之快速排序

    点击上方蓝色 "铁匠学编程" 关注我,让我们一起学习! 前天给大家分享了归并排序,但是它不是原地排序算法,需要消耗额外的内存空间,今天给大家分享的是江湖无人不知无人不晓的" ...

  7. 快速排序算法_常用排序算法专题—快速排序

    黑客技术点击右侧关注,了解黑客的世界! Linux编程点击右侧关注,免费入门到精通! 程序员严选甄选正品好物,程序员生活指南! 作者丨就是彬彬呀https://www.jianshu.com/p/17 ...

  8. 算法学习之排序(1)--插入排序

    一.算法描述 一般来说,插入排序都采用in-place在数组上实现.具体算法描述如下: 1. 从第一个元素开始,该元素可以认为已经被排序 2. 取出下一个元素,在已经排序的元素序列中从后向前扫描 3. ...

  9. 用c语言编写插入排序算法,C语言实现常用排序算法——插入排序

    插入排序是最基础的排序算法,原理: 首先1个元素肯定是有序的,所以插入排序从第二个元素开始遍历: 内循环首先请求一个空间保存待插入元素,从当前元素向数组起始位置反向遍历: 当发现有大于待插入元素的元素 ...

最新文章

  1. python 功能 代码_挑战“不可能”的代码:你不知道的Python功能
  2. 2018 东北地区大学生程序设计竞赛(ABEHIK)
  3. CentOS通过DNSpod实现动态域名
  4. sublime 消除锯齿_如何在Sublime中消除麻烦
  5. ik分词和jieba分词哪个好_Python 中文 文本分析 实战:jieba分词+自定义词典补充+停用词词库补充+词频统计...
  6. 疫情之后,人工智能该如何走?
  7. Bailian2708 平衡饮食【序列处理】
  8. Java工具-----native2ascii
  9. 第 7 章 Neutron - 078 - 实践 Neutron 前的两个准备工作
  10. GAN与自动编码器:深度生成模型的比较
  11. windows 编译libtorrent
  12. 文末彩蛋 | 这个 Request URL 长得好不一样
  13. java的graphics2d_Java Graphics2D类的绘图方法
  14. 终于有人把“内卷”和“囚徒困境”讲明白了!
  15. oracle database各个版本地址
  16. npm 发布包与遇见的问题 (随记)
  17. 恺撒密码是古罗马恺撒大帝用来对军事情报进行加解密的算法,它采用了替换方法对信息中的每一个英文字符循环替换为字母表序列中该字符后面的第三个字符,即,字母表的对应关系如下:
  18. 细胞培养常见问题分析
  19. 极进网络将收购斑马技术的无线局域网业务并获蓝筹客户
  20. vscode输入vue一键生成代码

热门文章

  1. 机器学习中的数学意义
  2. 走马观花AutoML
  3. 人工智能能否跨越意识鸿沟?
  4. SAP PI 7.3新特性介绍
  5. DeepMind论文:深度压缩感知,新框架提升GAN性能
  6. SAP EWM - 物料主数据 - EWM系统库存规划 - SLOTTING视图属性
  7. 数据科学工作者(Data Scientist) 的日常工作内容包括什么?
  8. 全息归约,全息算法。
  9. 扩散模型就是自动编码器!DeepMind研究学者提出新观点并论证
  10. 重磅,2020年度第十届吴文俊人工智能科学技术奖获奖名单公示