排序算法实践学习笔记|青训营

76 阅读3分钟

经典排序算法

插入排序

插入排序算法的实现原理是在列表当中选择第一个元素,然后进行比较,当后面的元素比前面的元素小时,两元素交换位置,返回以往,当遍历完整个列表时,序列排序完毕: 下面是一个序列的流程图

  • 初始状态: [5, 2, 4, 6, 1, 3]
  • 第一次迭代: [2, 5, 4, 6, 1, 3]
  • 第二次迭代: [2, 4, 5, 6, 1, 3]
  • 第三次迭代: [2, 4, 5, 6, 1, 3]
  • 第四次迭代: [1, 2, 4, 5, 6, 3]
  • 第五次迭代: [1, 2, 3, 4, 5, 6]

插入排序在数据基本有序的情况情况下时间复杂度最低是O(n),平均是O(n^2),最坏是O(n^2) 算法实现:

package main  
  
import "fmt"  
  
func insertsort(arr []int) {  
n := len(arr)  
  
for i := 1; i < n; i++ {  
key := arr[i]  
j := i - 1  
for j >= 0 && arr[j] > key {  
arr[j+1] = arr[j]  
j--  
}  
arr[j+1] = key  
}  
}  
func main() {  
arr := []int{5, 2, 4, 6, 1, 3}  
insertsort(arr)  
fmt.Println("Sorted array:", arr)  
}

快速排序

快速排序(Quick Sort)是一种常用的排序算法,它使用分治法(Divide and Conquer)策略来将一个数组分成两个子数组,然后递归地对子数组进行排序,最终将整个数组排序完成。 下面一组示例的演化过程:

  • 初始状态 :9, 5, 7, 2, 1, 8, 3, 6, 4
  • 第一次:4,5,7,2,1,8,3,6,9//基于第一个数字9进行第一次演化
  • 第二次:3,2,1,4,7,8,5,6,9//基于4进行演化,将比他小的放左边,大的放右边
  • 第三次:1,2,3,4,7,8,5,6,9//基于3进行演化,将比他小的放左边,大的放右边
  • 第四次:1,2,3,4,7,8,5,6,9//基于1进行演化,将比他小的放左边,大的放右边
  • 第五次:1,2,3,4,6,5,7,8,9//基于7进行演化,将比他小的放左边,大的放右边
  • 第五次:1,2,3,4,5,6,7,8,9//基于6进行演化,将比他小的放左边,大的放右边

快速排序最好情况时间复杂度:O(n log n),最坏情况时间复杂度:O(n^2)(当选择枢轴不平衡时)平均情况时间复杂度:O(n log n) 实现代码

func quicksort(arr []int) {  
if len(arr) <= 1 {  
return  
}  
pivot := arr[0]  
low := 1  
high := len(arr) - 1  
for low <= high {  
if arr[low] <= pivot {  
low++  
} else {  
arr[low], arr[high] = arr[high], arr[low]  
high--  
}  
}  
arr[0], arr[low-1] = arr[low-1], arr[0]  
quicksort(arr[:low-1])  
quicksort(arr[low:])  
}

堆排序

堆排序是利用构建最大堆或者是最小堆,交换根节点和最后一个节点:将堆的根节点与最后一个叶子节点交换,然后将最后一个节点排除在堆之外,相当于将最大值移到了已排序区域。然后在重新整理堆,再进行之前的操作,反复以往,当最后只剩最后一个时,完成对于排序列表的输出。

堆排序最好情况时间复杂度:O(n log n),最坏情况时间复杂度:O(n log n),平均情况时间复杂度:O(n log n)

package main  
  
import "fmt"  
  
// 调整堆,使其满足最大堆的性质  
func heapify(arr []int, n int, i int) {  
largest := i  
left := 2*i + 1  
right := 2*i + 2  
  
if left < n && arr[left] > arr[largest] {  
largest = left  
}  
if right < n && arr[right] > arr[largest] {  
largest = right  
}  
  
if largest != i {  
arr[i], arr[largest] = arr[largest], arr[i]  
heapify(arr, n, largest)  
}  
}  
  
// 堆排序  
func heapSort(arr []int) {  
n := len(arr)  
  
// 构建最大堆  
for i := n/2 - 1; i >= 0; i-- {  
heapify(arr, n, i)  
}  
  
// 交换根节点和最后一个节点,重建最大堆  
for i := n - 1; i > 0; i-- {  
arr[0], arr[i] = arr[i], arr[0]  
heapify(arr, i, 0)  
}  
}  
  
func main() {  
arr := []int{5, 2, 4, 6, 1, 3} 
fmt.Println("Unsorted array:", arr)  
  
heapSort(arr)  
  
fmt.Println("Sorted array:", arr)  
}

pdqsort

pdqsort算法是基于现有排序算法下,进行改进得到的算法,他将插入排序,快速排序,堆排序进行结合起来,使其在合适的阶段选择合适的排序算法,使得算法在何时都能保持在最差都有O(n log n)的时间复杂度,在情况好的时候甚至可以到O(n)

1691156324026.jpg pdqsort的排序实现流程图:

687474703a2f2f692e696d6775722e636f6d2f517a46473039462e676966.gif

但是第一版当中存在一些问题,比如说pivot的选择会导致效果不佳,如果使用首个,又或者遍历整个数组找到中位数,这又耗费更多的算力。 所以在下一个版本进行了pivot优化:

1691156955918.jpg

1691157115422.jpg 总结

  • 升级pivot选择策略(近似中位数)
  • 发现序列可能逆序,则翻转序列->应对reverse场景
  • 发现序列可能有序,使用有限插入排序->应对sorted场景