开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情
算法概述
十大算法推荐文章可以看一下 十大排序从入门到入赘 写的非常详细。
算法分类
十种常见排序算法可以分为两大类:
- 非线性时间比较类排序: 通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此成为非线性时间比较类排序。
- 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此成为线性时间非比较类排序。
算法复杂度
稳定与不稳定:如果a原本在b的前面并且a=b,排序之后a仍然在b的前面那就是稳定排序,如果排序之后a可能会出现在b的后面则是不稳定排序。
空间复杂度:是指算法在计算机内执行时所需的存储空间与n的规模之间的关系。
时间复杂度:排序的的总操作次数,与n的规模之间的关系。
冒泡排序
冒泡排序是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果大小顺序错误就交换过来。直到该数列交换完成,即排序完成。由于越小(大)的元素经过交换慢慢的“浮”到最上面,类似冒泡一样,名字也是因此得来。
func sort(items: Array<Int>) -> Array<Int> {
var list = items
for i in 0..<list.count {
for j in i+1..<list.count {
if list[j] > list[i] {
// 元素交换
let temp = list[j]
list[j] = list[i]
list[i] = temp
}
}
}
return list
}
选择排序
选择排序是一种简单直观的排序算法。工作原理是:先在未排序的序列中找到最小(最大)元素,存放到排序序列的起始位置,然后再从剩余的未排序的元素中寻找最小(最大)元素,然后放到已经排序序列的末尾。以此类推,直到所有元素排序完毕。
func sort(items: Array<Int>) -> Array<Int> {
var list = items
for i in 0..<list.count {
//记录当前最小的数,比较i+1后更大的数进行记录
var minIndex = i
for j in i+1..<list.count {
if list[j] < list[minIndex] {
minIndex = j
}
}
// 交换
let temp = list[minIndex]
list[minIndex] = list[i]
list[i] = temp
}
return list
}
插入排序
插入排序的算法是一种简单直观的排序算法。原理是通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入。类似于玩扑克牌时摸完牌的操作。直到全部排序完成。
func sort(items: Array<Int>) -> Array<Int> {
var list = items
//循环无序数列
for i in 1..<list.count {
var j = i
//循环有序数列,插入相应的值
while j > 0 {
if list[j] < list[j - 1] {
let temp = list[j]
list[j] = list[j-1]
list[j-1] = temp
j = j - 1
} else {
break
}
}
}
return list
}
希尔排序
希尔排序是简单插入排序的改进版,先将待排序的序列根据步长分割成若干个子序列,分别取出相应位置元素进行比对并交换,全部比对完成后缩小步长并重复前面的步骤,直到步长为1为止。
func sort(items: Array<Int>) -> Array<Int> {
var list = items
guard list.count > 1 else {
return list
}
// 步长
var step = list.count / 2
while step > 0 {
for i in step..<list.count {
var formIndex = i - step
while formIndex >= 0 {
if list[formIndex] > list[formIndex + step] {
let temp = list[formIndex]
list[formIndex] = list[formIndex + step]
list[formIndex + step] = temp
}
formIndex = formIndex - step
}
}
// 缩小步长
step = step / 2
}
return list
}
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个典型应用。将已经有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。如果将两个有序表合并成一个有序表,称为2路归并。
归并排序用一句话来说就是:先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并。
// 定义:将子数组nuns[lo..hi]进行排序
func sort(nums: inout [Int], lo: Int, hi: Int) {
if lo == hi {
// 单个元素不用排序
return
}
// 这样写是为了防止溢出,效果等同于 (hi + lo)/2
let mid = lo + (hi - lo) / 2
// 先对左半部分数组nums[lo..mid]排序
sort(nums: &nums, lo: lo, hi: mid)
// 再对右半部分数组nums[mid+1..hi]排序
sort(nums: &nums, lo: mid+1, hi: hi)
// 将两部分有序数组合并成一个有序数组
merge(nums: &nums, lo: lo, mid: mid, hi: hi)
}
// 将 nums[lo..mid] 和 nums[mid+1..hi] 这两个有序数组合并成一个有序数组
func merge(nums: inout [Int], lo: Int, mid: Int, hi: Int) {
// 先把nums[lo..hi]复制到辅助数组中
// 以便合并后的结果能够直接存入nums
for i in lo...hi {
temp[i] = nums[i]
}
// 数组双指针技巧,合并两个有序数组
var i = lo
var j = mid + 1
for p in lo...hi {
if i == mid + 1 {
// 左半边数组已全部被合并
nums[p] = temp[j]
j += 1
}else if j == hi + 1 {
// 右半边数组已全部被合并
nums[p] = temp[i]
i += 1
}else if temp[i] > temp[j] {
nums[p] = temp[j]
j += 1
}else {
nums[p] = temp[i]
i += 1
}
}
}
var temp: [Int] = []
func sort(items: Array<Int>) -> Array<Int> {
temp = Array(repeating: 0, count: nums.count)
var nums = items
sort(nums: &nums, lo: 0, hi: nums.count-1)
return nums
}
快速排序
快速排序的基本思想:先在序列中挑出一个基准,把所有比基准小的元素放在基准前面,所有比基准大的元素放到基准后面,这个称为分区操作,每个分区重复上面的步骤。
用一句话来说快排就是:先将一个元素排好序,然后再将剩下的元素排好序。
快速排序其实就是一个二叉树的前序遍历。
/// 6.快速排序
func sort(items: Array<Int>) -> Array<Int> {
var list = items
quickSort(list: &list, low: 0, high: list.count-1)
return list
}
// 将数组以第一个值为准分成两部分,前半部分比该值要小,后半部分比该值要大
func parition(list: inout Array<Int>, low: Int, high: Int) -> Int {
var low = low
var high = high
let temp = list[low]
while low < high {
while list[high] >= temp && low < high {
high -= 1
}
list[low] = list[high]
while list[low] <= temp && low < high {
low += 1
}
list[high] = list[low]
}
list[low] = temp
return low
}
func quickSort(list: inout Array<Int>, low: Int, high: Int) {
if low < high {
let mid = parition(list: &list, low: low, high: high)
quickSort(list: &list, low: low, high: mid-1)
quickSort(list: &list, low: mid+1, high: high)
}
}
堆排序
完全二叉树结果,将堆顶元素与最后一个元素交换,然后调整新堆,然后再将堆顶与无序区最后一个元素交换,调整新堆,直到全部排序完成。
func sort(items: Array<Int>) -> Array<Int> {
var list = items
//创建大顶堆,其实就是将list转换成大顶堆层次的遍历结果
heapCreate(items: &list)
var endIndex = items.count - 1
while endIndex >= 0 {
//将大顶堆的顶点(最大的那个值)与大顶堆的最后一个值进行交换
let temp = list[0]
list[0] = list[endIndex]
list[endIndex] = temp
endIndex -= 1 //缩小大顶堆的范围
//对交换后的大顶堆进行调整,使其重新成为大顶堆
heapAdjast(items: &list, startIndex: 0,endIndex: endIndex + 1)
}
return list
}
///构建大顶堆的序列
func heapCreate(items: inout Array<Int>) {
var i = items.count
while i > 0 {
heapAdjast(items: &items, startIndex: i - 1, endIndex:items.count )
i -= 1
}
}
/// 对大顶堆的局部进行调整,使其该节点的所有父类符合大顶堆的特点
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 = maxChildIndex
maxChildIndex = 2 * fatherIndex
}
items[fatherIndex-1] = temp
}
计数排序
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
func sort(items: Array<Int>) -> Array<Int> {
// 计数排序数组用简单的数据
let list: Array<Int> = [2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2]
// 创建0~9的数组,每一项里是个可变数组
var listArray: Array<Array<Int>> = [[], [], [], [], [], [], [], [], [],[]]
for item in 0..<list.count {
listArray[list[item]].append(list[item])
}
var finalArray = Array<Int>()
for i in 0..<listArray.count {
let array = listArray[i]
for j in 0..<array.count {
let item = array[j]
finalArray.append(item)
}
}
return finalArray
}
桶排序
桶排序是计数排序的升级版。桶排序的原理:设置一个定量的数组当做空桶,遍历输入数据,并且把数据一个一个放到对应的桶里去;对每个不是空的桶进行排序;从不是空的桶里把排序好的额数据拼接起来。
func sort(items: Array<Int>) -> Array<Int> {
// 桶排序数组用简单的数据
let list = items
var minValue = list[0]
var maxValue = list[0]
for item in 0..<list.count {
if list[item] < minValue {
// 数据的最小值
minValue = list[item]
}else if list[item] > maxValue {
// 数据的最大值
maxValue = list[item]
}
}
var finalArray = Array<Int>(repeating: 0, count: maxValue-minValue+1)
for item in 0..<list.count {
var index = finalArray[list[item]-minValue]
index = index + 1
finalArray[list[item]-minValue] = index
}
var resultArray = Array<Int>()
for item in 0..<finalArray.count {
if finalArray[item] > 0 {
for _ in 0..<finalArray[item] {
resultArray.append(item+minValue)
}
}
}
return resultArray
}
基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
func sort(items: Array<Int>) -> Array<Int> {
var list = items
if list.count > 0 {
radixSort(list: &list)
}
return list
}
private func radixSort(list: inout Array<Int>) {
var bucket = createBucket()
let maxNumber = listMaxItem(list: list)
let maxLength = numberLength(number: maxNumber)
for digit in 1...maxLength {
//入桶
for item in list {
let baseNumber = fetchBaseNumber(number: item, digit: digit)
bucket[baseNumber].append(item) //根据基数进入相应的桶中
}
//出桶
var index = 0
for i in 0..<bucket.count {
while !bucket[i].isEmpty {
// 以队列形式
list[index] = bucket[i].remove(at: 0)
index += 1
}
}
}
}
/// 创建10个桶
private func createBucket() -> Array<Array<Int>> {
var bucket: Array<Array<Int>> = []
for _ in 0..<10 {
bucket.append([])
}
return bucket
}
/// 计算无序序列中最大的那个数
private func listMaxItem(list: Array<Int>) -> Int {
var maxNumber = list[0]
for item in list {
if maxNumber < item {
maxNumber = item
}
}
return maxNumber
}
/// 获取数字的长度
func numberLength(number: Int) -> Int {
return "\(number)".count
}
/// 获取相应位置的数字(number: 操作的数字,digit: 位数)
func fetchBaseNumber(number: Int, digit: Int) -> Int{
if digit > 0 && digit <= numberLength(number: number) {
var numbersArray: Array<Int> = []
for char in "\(number)" {
numbersArray.append(Int("\(char)")!)
}
return numbersArray[numbersArray.count - digit]
}
return 0
}