排序算法总览

话不多说,直接看一下维基百科的总结图

十大排序算法

补充一点排序算法稳定性的定义:

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
  • 排序算法是否为稳定的,是由具体算法决定的

基本数据结构

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

展开讲讲

冒泡排序

  • 要点:

    • 需要进行n(待排序数据个数)趟比较
    • 每一趟都从头开始,依次往后比较相邻数据的大小,根据自己的需求进行交换
    • 每一趟都会找出当前无序区的最大或者最小数据,加入到有序区中
  • 使用场景:

    • 教学
    • 小数据量排序
    • 数据基本有序时
func Bubble(data Interface) {
    for i := 0; i < data.Len(); i++ {
        flag := true
        for j := 0; j < data.Len()-i-1; j++ {
            if data.Less(j, j+1) {
                continue
            }
            data.Swap(j, j+1)
            flag = false
        }
        if flag {
            break
        }
    }
}

选择排序

  • 要点:

    • 需要进行n(待排序数据个数)趟选择
    • 每一趟执行过程只比较数据,不交换数据
    • 每一趟都会找出当前无序区的最大或者最小数据,将该数据与无序区的第一个元素交换,同时减小无序区间
  • 使用场景:

    • 教学
    • 小数据量排序
func Selection(data Interface) {
    var index int
    for i := 0; i < data.Len()-1; i++ {
        index = i
        for j := i + 1; j < data.Len(); j++ {
            if !data.Less(index, j) {
                index = j
            }
        }
        data.Swap(i, index)
    }
}

插入排序

  • 要点:

    • 与打牌时整理牌的思路是一样的
    • 手头是有序的牌,桌面是无序的牌
    • 每次从桌面拿一张牌,然后跟手中的牌比较,插入合适的位置
  • 使用场景:

    • 小数据量排序
    • 数据基本有序时
func Insertion(data Interface) {
    for i := 1; i < data.Len(); i++ {
        for j := i - 1; j >= 0 && data.Less(j+1, j); j-- {
            data.Swap(j+1, j)
        }
    }
}

希尔排序

  • 要点:

    • 有“步长”的插入排序
    • 正常的插入排序,可以理解成步长为1
    • 希尔排序有一个最优的步长序列(1, 5, 19, 41, 109,...)
    • 数据基本有序时,插入排序效率高,所以通过步长加速数据的有序化
    • 参考维基百科
  • 使用场景:

    • 属于插入排序的优化升级版本
    • 小数据量
// shell sort (希尔排序,递减增量排序) 
// 已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...)
func Shell(data Interface) {
    // 步长,只适合小数据量
    step := 5

    if data.Len() < 5 {
        goto END
    }

    for i := 0; i < step && i < data.Len(); i++ {
        for j := i - step; j >= 0 && data.Less(j+step, j); j -= step {
            data.Swap(j+step, j)
        }
    }
    // 函数调用会有耗时, 所以直接实现一小插入排序
    //Insertion(data)
END:
    for i := 1; i < data.Len(); i++ {
        for j := i - 1; j >= 0 && data.Less(j+1, j); j-- {
            data.Swap(j+1, j)
        }
    }
}

(自己简单测试一下,十个数据,与插入排序对比,确实有微秒级的提升)

快速排序

  • 要点:

    • 需要选取一个基准值,方法很多,通常选取序列的第一个元素
    • 双指针,一个指向头,一个指向尾
    • 最终目的是将一个序列分成两段,一段比基准值小,另一段比基准值大
    • 与基准值相等的元素,根据自己的规则,放在基准值的左边或右边
    • 然后对每一段继续进行如上操作
    • 分治的思想
    • 快速排序的最坏运行情况是 O(n²) (序列基本有序的情况)
    • 它的平摊期望时间是 O(nlogn)
  • 使用场景:

    • 相对大的数据量
    • 在大多数情况下比其他算法性能都要好
// quick sort (快排)
func Quick(data Interface) {
    quickSort(data, 0, data.Len())
}

func quickSort(data Interface, s, e int) {
    if e-s < 2 {
        return
    }

    i, j := s, e-1
    // 基准,这里取第一个元素
    pivot := i

    // 一趟快排
    for i < j {
        // 从后向前比较
        for ; i < j; j-- {
            if !data.Less(j, pivot) {
                continue
            }
            data.Swap(pivot, j)
            pivot = j
            i++
            break
        }

        // 从前向后比较
        for ; i < j; i++ {
            if data.Less(i, pivot) {
                continue
            }
            data.Swap(pivot, i)
            pivot = i
            j--
            break
        }
    }

    // 递归
    quickSort(data, s, pivot)
    quickSort(data, pivot+1, e)
}

(未完待续……)


satan
16 声望1 粉丝

深呼吸,然后静下来。