面试必问的排序算法

欢迎大家到我的博客浏览。排序算法 | YinKai's Blog

介绍

在面试中被问到排序算法,发现不能很好的描述出来,于是写一篇文章总结一下。常见的排序算法有 冒泡排序插入排序快速排序归并排序堆排序等。下面一一介绍。

1、冒泡排序 O(n^2)
算法思想:

共 n 个元素需要排序,通过比较相邻的两个元素,并将较大的元素放到后面,以此每一轮比较将乱序的元素中的最大的元素放到最后一个有序区间中。每一次遍历都能确定一个数在有序数组中的位置,且数组最后的一个区间中的数是有序的,直到所有数的位置都确定了,排序完成。

核心代码:
for i := 0; i < n-1; i++ {
    for j := 0; j < n-i-1; j++ {
        if arr[j] > arr[j+1] {
            // 交换 arr[j] 和 arr[j+1]
            arr[j], arr[j+1] = arr[j+1], arr[j]
        }
    }
}
2、插入排序 O(n^2)
算法思想:

遍历一遍整个数组,每次遍历将当前元素放到数组首部的有序序列中的合适位置,每次保证数组首部增加一个数,遍历 n - 1 次后,就可以保证整个数组的有序性。【合适的位置:往前找第一个小于当前数的位置,将其放在该数的后面,就说明当前数前面小于当前数,当前数后面大于当前数】

核心代码:
for i := 1; i < n; i++ {
    key := q[i] // 注意:在Go中切片的使用
    j := i - 1

    // 往前找第一个小于当前数的位置
    // 将其放在该数的后面,说明当前数前面小于当前数,当前数后面大于当前数
    for j >= 0 && q[j] > key {
        q[j+1] = q[j]
        j--
    }

    q[j+1] = key
}
3、快速排序 O(nlogn)
算法思想:

分而治之的一个思想,每次将整个数组分为两部分,然后保证左边是小于某个值的,右边是大于某个值的,这样在对两个部分采用同样的思想去划分,最后划分到区间中只有一个数了,就可以保证整个数组是有序的了。

核心代码:
func quickSort(q []int, l, r int) {
    if l >= r {
        return 
    }
​
    mid := (l + r) >> 1
    x := q[mid]
    i, j := l - 1, r + 1
    for i < j {
        for i ++; q[i] < x; i ++{}
        for j --; q[j] > x; j --{}
        if i < j {
            q[i], q[j] = q[j], q[i]
        }
    }
    quickSort(q, l, j)
    quickSort(q, j + 1, r)
}
4、归并排序 O(nlogn)
算法思想:

利用递归将整个区间分为足够小的区间,保证小区间中的数是有序的,然后再逐渐回溯,使大区间也变得有序。最终达到整个区间有序的一个状态。

核心代码:
func mergeSort(a []int, l, r int) {
    if l >= r {
        return
    }
​
    mid := (l + r) >> 1
    mergeSort(a, l, mid)
    mergeSort(a, mid+1, r)
    i, j, k := l, mid+1, 0
    for i <= mid && j <= r {
        if a[i] <= a[j] {
            t[k] = a[i]
            k++
            i++
        } else {
            t[k] = a[j]
            k++
            j++
        }
    }
​
    for i <= mid {
        t[k] = a[i]
        k++
        i++
    }
    for j <= r {
        t[k] = a[j]
        k++
        j++
    }
    for i, j := l, 0; i <= r; i++ {
        a[i] = t[j]
        j++
    }
}
5、堆排序
算法思想

堆:即一棵完全二叉树,且每一个点小于等于其左右儿子节点【小根堆】。

建堆:从总数的 n/2 开始往上建,因为必须保证其有左右儿子节点。建堆的过程,就是把父节点、左右儿子节点中较小的元素放到父节点的位置,逐步将大的节点往下移的操作。

核心代码
package main
​
​
import "fmt"
​
var n, m, cnt int
var h []int
​
func down (u int) {
    t := u
    if u * 2 <= cnt && h[u * 2] < h[t] {
        t = u * 2
    }
    if u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t] {
        t = u * 2 + 1
    }
    if t != u {
        h[t], h[u] = h[u], h[t]
        down(t)
    }
}
​
func main() {
    fmt.Scan(&n, &m)
    h = make([]int, n + 1)
    
    for i := 1; i <= n; i ++ {
        fmt.Scan(&h[i])
    }
    cnt = n
    
    for i := n / 2; i > 0; i -- {
        down(i)
    }
    for n > 0 {
        n --
        fmt.Printf("%d ", h[1])
        h[1] = h[cnt]
        cnt --
        down(1)
    }
}

未完待续。。。。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胤凯o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值