算法编程题-排序

算法编程题-排序

    • 比较型排序算法
      • 冒泡排序
      • 选择排序
      • 插入排序
      • 希尔排序
      • 堆排序
      • 快速排序
      • 归并排序
    • 非比较型排序算法
      • 计数排序
      • 基数排序

本文将对七中经典比较型排序算法进行介绍,并且给出golang语言的实现,还包括基数排序、计数排序等非比较型的算法的介绍和实现。

比较型排序算法

所谓的比较型排序算法就是算法中会使用数据之间的比较,只能数组保存的是能相关比较大小的数据即可使用该类算法,相比于非比较型排序算法适用面更广。
在实现上进行了一定的封装,支持泛型和自定义排序规则,默认传入一个比较函数less,按照less指定的小于关系进行“从小到大”排序。抽象接口代码如下:

type Sorter[T any] struct {
   
	less func(item1, item2 T) bool
}

func NewSorter[T any](less func(item1, item2 T) bool) *Sorter[T] {
   
	return &Sorter[T]{
   less: less}
}

冒泡排序

冒泡排序如其名,就是排序的过程和冒泡有点像。一个气泡从水底浮向水面,其体积是越来越大的。那么同理,在排序的过程中,也可以让一个个大的元素浮上去,从而完成整个的排序。代码实现如下:

// BubbleSort 冒泡排序,时间复杂度:O(n^2) 空间复杂度:O(1), 稳定
func (s *Sorter[T]) BubbleSort(arr []T) {
   
	n := len(arr)
	for i := 0; i < n; i++ {
   
		var bubbleFlag bool // 是否有冒泡
		for j := i; j+1 < n; j++ {
   
			if s.less(arr[j+1], arr[j]) {
   
				arr[j], arr[j+1] = arr[j+1], arr[j]
				bubbleFlag = true
			}
		}
		if !bubbleFlag {
    // 无冒泡,说明已经排序完成,提前退出
			break
		}
	}
}

冒泡排序时间复杂度是 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1)。其是一种稳定的排序算法。在实现上可以记录一个标志,表明此一轮有没有发生冒泡,如果没有,说明数组已经完全有序,可以以前退出。

选择排序

选择排序同样也是人如其名,在每一轮中,选择剩余数组中的最小值,放入一个位置,经过n轮,完成排序。实现代码如下:

// SelectSort 选择排序,时间复杂度:(n ^ 2), 空间复杂度:O(1),不稳定
func (s *Sorter[T]) SelectSort(arr []T) {
   
	n := len(arr)
	for i := 0; i < n; i++ {
   
		minPtr := i
		for j := i + 1; j < n; j++ {
   
			if s.less(arr[j], arr[minPtr]) {
   
				minPtr = j
			}
		}
		arr[minPtr], arr[i] = arr[i], arr[minPtr]
	}
}

选择排序时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1),是一种不稳定的排序算法。

插入排序

插入排序的排序过程为:维护一个有序序列,然后将待排序的数字找到一个合适的位置,然后插入进去,这样还是一个有序序列,依次将所有元素完成这一个操作,即可完成数组的排序。实现代码如下:

// InsertSort 插入排序,时间复杂度:O(n^2) 空间复杂度:O(1), 稳定
func (s *Sorter[T]) InsertSort(arr []T) {
   
	n := len(arr)
	for i := 1; i < n; i++ {
   
		for j := i; j > 0; j-- {
   
			if s.less(arr[j], arr[j-1]) {
   
				arr[j], arr[j-1] = arr[j-1], arr[j]
			} else {
   
				break
			}
		}
	}
}

插入排序的时间复杂度平均为 O ( n 2 ) O(n^2) O(n2),但是对于一个基本有序的数组进行排序,时间复杂度可以达到 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1),是一种稳定的排序算法。
优化插入排序,可以从两个方面入手,减少找位置的时间或者移动元素的时间,每一轮这两个的时间都是 O ( n ) O(n) O(n)级别的,前者可以通过二分查找来优化,后者可以通过链表来优化,但是如何同时优化呢?笔者认为可以使用跳表,这样可以将插入排序的平均时间复杂度优化到 O ( n l o g n ) O(nlogn) O(nlogn),但是缺点在于实现复杂,且空间开销较大,相比于快速排序堆排序等,有点得不偿失。

希尔排序

希尔排序其实就是对于插入排序的优化,前文提到,插入排序在基本有序的数组排序效率比较高,那么可以考虑对于数组的子序列进行排序,具体的思路就是将子数组分为多个子序列,在每一轮中只对子序列内部进行插入排序。子序列分组的规则是每隔gap个的元素作为一个子序列,gap值在轮数下不断减小,直到为一后相当于是对全部数据进行插入排序,由于此时数组已经基本有序,所以最后一轮插入排序的效率很高。

// 希尔排序,时间复杂度: O(n^(3 / 2)) 空间复杂度:O(1), 不稳定
func (s *Sorter[T]) ShellSort(arr []T) {
   
	n := len(arr)
	gap := n >> 1
	for gap > 0 {
   
		for i := 0; i < gap; i++ {
   
			for j := i; j < n; j += gap {
   
				for k := i + gap; k < n; k 

你可能感兴趣的:(算法编程题,算法,排序算法,数据结构,golang)