文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

图解算法基础-快速排序,附 Go 代码实现

2024-12-02 05:21

关注

文中算法的实现是用 Go 写了一个比较简单的快速排序,方便大家理解(旁边画外音:其实是他好几年没面试了,太厉害的他也写不出来)。

关于更优秀的代码实现,可以在评论区里发出来一起学习,相信咱们读者里一定是卧虎藏龙,有不少算法大拿。

快速排序的思想

快速排序算法首先会在序列中随机选择一个基准值(pivot),然后将除了基准值之外的数分为 "比基准值小的数" 和 "比基准值大的数" 这两个类别,再将其排列成以下形式。

【比基准值小的数】 基准值 【比基准值大的数】

接着,继续对两个序列 "【】"中的数据进行排序之后,整体的排序便完成了。对基准值左右两侧的序列排序时,同样也会使用快速排序。

快速排序是一种"分治法",将原本的问题分解成两个子问题—— 比基准值小的数和比基准值大的数,然后再分别解决这两个子问题。解决子问题的时候会再次使用快速排序,只有在子问题里只剩下一个数字的时候,排序才算完成。

快排的过程

下面我们用示意图更好地理解一下快速排序对一个序列进行排序的过程。

图例出自—《我的第一本算法书》

假定有如下待排序序列:

待排序序列

首先在序列中随机选择一个基准值,这里选择了 4。

选择基准值 pivot

将其他数字和基准值进行比较,小于基准值的往左移,大于基准值的往右移。

首先比较第一个元素 3 和基准值4,因为 3 < 4, 所以将 3放在基准值的左边。

首先比较 3 和基准值4,因为 3 < 4, 所以将 3放在基准值的左边

接下来,比较 5 和基准值,因为 5 > 4,所以将 5 放在基准值的右边。

5 > 4, 将5放在基准值右边

对整个序列进行同样操作后,所有小于基准值的数字全都放到了基准值的左边,大于的则全都放在了右边。

一轮排序完成后的结果

把基准值放入序列

现在排序就分成了两个子问题,分别再对基准值左边和右边的数据进行排序。

分解成了两个子问题

两边的排序操作也和前面的一样,也是使用快排算法,选取基准值,把小于的数字放左边大于的放右边。

对子序列使用快速排序

子问题有可能会再分解成子问题,直到子问题里只剩下一个数字,再也无法分解出子问题的时候,整个序列的排序才算完成。

排序完成

因为快速排序算法在对序列进行排序的过程中会再次使用该算法,所以快速排序算法在实现时需要使用"递归”来实现。

快速排序的Go代码实现

下面上一个用 Go 版本的快速排序算法的简单实现:

func quickSort(sequence []int, low int, high int) {
if high <= low {
return
}
j := partition(sequence, low, high)
quickSort(sequence, low, j-1)
quickSort(sequence, j+1, high)
}

// 进行快速排序中的一轮排序
func partition(sequence []int, low int, high int) int {
i, j := low+1, high
for {
// 把头元素作为基准值 pivot
for sequence[i] < sequence[low] {
// i 坐标从前往后访问序列,如果位置上的值大于基准值,停下来。
// 准备和 j 坐标访问到的小于基准值的值交换位置
i++
if i >= high {
break
}
}
for sequence[j] > sequence[low] {
// j 坐标从后往前访问序列,如果位置上的值小于基准值,停下来。
// 和 i 坐标指向的大于基准值的值交换位置
j--
if j <= low {
break
}
}
if i >= j {
break
}
sequence[i], sequence[j] = sequence[j], sequence[i]
}
sequence[low], sequence[j] = sequence[j], sequence[low]

return j
}

每一轮快速排序都会经历下面这几个步骤:

  1. 设置两个变量i、j,排序开始的时候:i=0,j=待排序序列长度 - 1。
  2. 以第一个数组元素作为基准值 pivot(也可以是最后一个元素,或者是随机的一个元素)。
  3. i 坐标从开始向后访问序列里的元素,即 i++,找到第一个大于 pivot 的位置 ,和 j 坐标访问到的小于基准值的值交换位置。
  4. j 坐标从末尾向前搜索,即j--,找到第一个小于 pivot 的位置,将i,j坐标上的值进行互换。
  5. 重复第3、4步,直到i=j,然后将 pivot 和 j 坐标上的值互换,完成一轮排序,小于 pivot 的值都放在了它的左边,大于的则放到了右边。

重复进行上面的过程,直到排序完成。最后我们可以生成一个随机数序列对上面的快速排序函数进行测试:

func main() {
rand.Seed(time.Now().Unix())
sequence := rand.Perm(34)
fmt.Printf("sequence before sort: %v", sequence)
quickSort(sequence, 0, len(sequence) - 1)
fmt.Printf("sequence after sort: %v", sequence)
}

快速排序的时间复杂度

分割子序列时需要选择基准值,如果每次选择的基准值都能使得两个子序列的长度为原本的一半,那么快速排序的运行时间和归并排序的一样,都为 O(nlogn)。将序列对半分割 log2n 次之后,子序列里便只剩下一个数据,这时子序列的排序也就完成了。

因此,如果像下图这样一行行地展现根据基准值分割序列的过程,那么总共会有 log2n 行。

快排分解序列的次数

每行中每个数字都需要和基准值比较大小,因此每行所需的运行时间为 O(n)。由此可知,整体的时间复杂度为 O(nlogn)。

如果运气不好,每次都选择最小值作为基准值,那么每次都需要把其他数据移到基准值的右边,递归执行 n 行,运行时间也就成了。

所以真正应用的时候基准值的选取也比较讲究,比如以中位数做基准值:本轮排序序列的第一个、最后一个、中间位置三个数的中位数作为基准值进行排序。

来源:网管叨bi叨内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯