0

0

使用Go语言切片实现原地快速排序

心靈之曲

心靈之曲

发布时间:2025-10-05 10:42:34

|

953人浏览过

|

来源于php中文网

原创

使用Go语言切片实现原地快速排序

本文旨在介绍如何在Go语言中实现一个地道的原地快速排序算法。我们将利用Go语言切片(slices)的特性、简洁的交换语法以及递归机制,展示一种高效且符合Go语言习惯的排序方法,深入理解Go在处理动态数组和原地操作方面的优势。

快速排序算法概述

快速排序(quicksort)是一种高效的、基于比较的排序算法,采用分治(divide and conquer)策略。其基本思想是:

  1. 选择枢轴(Pivot):从待排序数组中选择一个元素作为枢轴。
  2. 分区(Partition):重新排列数组,将所有小于枢轴的元素移到枢轴的左边,所有大于枢轴的元素移到枢轴的右边。枢轴位于最终位置,此时左右两边形成了两个子数组。
  3. 递归排序:对左右两个子数组递归地重复上述步骤,直到子数组的长度小于或等于1。

快速排序的平均时间复杂度为O(N log N),但在最坏情况下可能达到O(N^2)。由于其原地(in-place)特性,它在内存使用上非常高效。

Go语言中的切片与原地操作

在Go语言中,切片([]T)是构建在数组之上的一个抽象,它提供了动态大小和灵活的视图。与固定大小的数组不同,切片可以方便地进行扩展、截取和传递,而底层数据通常保持不变。快速排序通常需要对数据进行原地修改和子数组操作,Go语言的切片非常适合这种场景:

  • 动态性:切片可以表示任意长度的序列。
  • 引用语义:切片在函数间传递时,传递的是切片头信息(指针、长度、容量),而不是底层数组的副本。这意味着对切片元素的修改会直接反映在原始数据上,天然支持原地操作。
  • 子切片:Go提供了简洁的语法 a[low:high] 来创建子切片,这使得递归地处理子数组变得非常直观和高效。

地道的Go语言快速排序实现

以下是一个使用Go语言切片实现的快速排序函数,它遵循了Lomuto分区方案,并利用了Go语言的一些惯用特性:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

// qsort 对整数切片进行原地快速排序
func qsort(a []int) []int {
    // 基线条件:如果切片长度小于2,则无需排序,直接返回
    if len(a) < 2 {
        return a
    }

    // 初始化左右指针
    left, right := 0, len(a)-1

    // 1. 选择枢轴:这里简单地随机选择一个元素作为枢轴
    // 注意:更健壮的实现会使用“三数取中”等策略
    rand.Seed(time.Now().UnixNano()) // 确保每次运行随机数不同
    pivotIndex := rand.Intn(len(a)) // rand.Intn(n) 返回 [0, n) 的随机整数

    // 2. 将枢轴移动到最右端,方便后续分区操作
    a[pivotIndex], a[right] = a[right], a[pivotIndex]

    // 3. 分区操作:将小于枢轴的元素移到左边
    // 遍历切片,将小于枢轴的元素与left指针指向的元素交换
    for i := range a {
        // 枢轴当前在a[right]
        if a[i] < a[right] {
            a[i], a[left] = a[left], a[i]
            left++ // left指针向前移动,指向下一个待交换位置
        }
    }

    // 4. 将枢轴放回其最终位置
    // 此时,left指针指向第一个大于或等于枢轴的元素位置
    // 将枢轴(原a[right])与a[left]交换
    a[left], a[right] = a[right], a[left]

    // 5. 递归排序左右子数组
    qsort(a[:left])       // 排序左子数组 (不包含枢轴)
    qsort(a[left+1:])     // 排序右子数组 (不包含枢轴)

    return a
}

func main() {
    arr1 := []int{9, 2, 5, 1, 7, 3, 8, 4, 6}
    fmt.Printf("Original array: %v\n", arr1)
    qsort(arr1)
    fmt.Printf("Sorted array:   %v\n", arr1) // 输出: Sorted array:   [1 2 3 4 5 6 7 8 9]

    arr2 := []int{3, 1, 4, 1, 5, 9, 2, 6}
    fmt.Printf("Original array: %v\n", arr2)
    qsort(arr2)
    fmt.Printf("Sorted array:   %v\n", arr2) // 输出: Sorted array:   [1 1 2 3 4 5 6 9]

    arr3 := []int{10}
    fmt.Printf("Original array: %v\n", arr3)
    qsort(arr3)
    fmt.Printf("Sorted array:   %v\n", arr3) // 输出: Sorted array:   [10]

    arr4 := []int{}
    fmt.Printf("Original array: %v\n", arr4)
    qsort(arr4)
    fmt.Printf("Sorted array:   %v\n", arr4) // 输出: Sorted array:   []
}

代码解析

  1. 基线条件 if len(a) : 这是递归算法的关键。如果切片的长度小于2(即空切片或只有一个元素的切片),则它已经有序,无需进一步处理,直接返回。

  2. 选择枢轴与移动:

    立即学习go语言免费学习笔记(深入)”;

    VanceAI Image Resizer
    VanceAI Image Resizer

    VanceAI推出的在线图片尺寸调整工具

    下载
    • pivotIndex := rand.Intn(len(a)):从切片中随机选择一个索引作为枢轴的初始位置。为了避免每次运行结果相同,我们使用了 rand.Seed(time.Now().UnixNano()) 来初始化随机数生成器。
    • a[pivotIndex], a[right] = a[right], a[pivotIndex]:将选定的枢轴元素与切片的最右端元素交换。这样做是为了简化后续的分区逻辑,让枢轴暂时离开分区区域,待分区完成后再放回正确位置。Go语言的这种多重赋值语法是进行元素交换的惯用且简洁的方式。
  3. 分区操作 for i := range a { ... }:

    • left 指针用于标记当前已分区区域中,所有小于枢轴的元素的边界。
    • for i := range a 遍历切片中的所有元素(除了最右端的枢轴,但由于 range a 会遍历所有元素,实际操作中枢轴会被跳过,因为它在 a[right])。
    • if a[i]
    • a[i], a[left] = a[left], a[i]:再次利用Go语言的简洁交换语法。
    • left++:left 指针向前移动,为下一个小于枢轴的元素腾出位置。
  4. 枢轴归位 a[left], a[right] = a[right], a[left]: 当 for 循环结束后,left 指针指向的位置是第一个大于或等于枢轴的元素,或者所有元素都小于枢轴时的切片末尾。此时,将之前放置在 a[right] 的枢轴元素与 a[left] 处的元素交换,枢轴便回到了它最终的正确位置。此时,left 指针也恰好是枢轴的最终索引。

  5. 递归调用 qsort(a[:left]) 和 qsort(a[left+1:]):

    • a[:left] 创建了一个新的切片,它引用了原始切片从开始到 left-1 的所有元素(即枢轴左侧的子数组)。
    • a[left+1:] 创建了一个新的切片,它引用了原始切片从 left+1 到末尾的所有元素(即枢轴右侧的子数组)。
    • 对这两个子切片进行递归调用,直到满足基线条件。由于切片是引用类型,这些操作都是在原始切片上进行的,实现了原地排序。

注意事项与优化

  • 枢轴选择策略: 本示例采用了随机选择枢轴。虽然简单,但在某些特定输入(如已部分排序或逆序的数组)下,随机选择仍可能导致最坏情况(O(N^2))的发生。更健壮的实现通常会采用“三数取中”(Median-of-three)策略,即选择切片首、尾和中间三个元素的中位数作为枢轴,以提高算法在各种输入下的平均性能。
  • 小规模子数组优化: 对于非常小的子数组(例如长度小于10-20),快速排序的递归开销可能大于其他简单排序算法(如插入排序)。在实际应用中,可以设置一个阈值,当子数组长度小于该阈值时,切换到插入排序,以减少递归调用的开销并提高效率。
  • 并发性: 快速排序的两个递归调用 qsort(a[:left]) 和 qsort(a[left+1:]) 是相互独立的,这意味着它们可以并行执行。Go语言的 goroutine 和 channel 机制非常适合实现这种并行化。通过将子任务提交给不同的goroutine,可以在多核处理器上显著提高排序速度。
  • 稳定性: 快速排序通常不是一个稳定的排序算法。这意味着如果数组中存在相等的元素,它们在排序后的相对顺序可能与排序前不同。如果需要保持相等元素的相对顺序,则需要选择其他排序算法(如归并排序)或对快速排序进行特殊处理。

总结

通过上述示例,我们展示了如何在Go语言中实现一个地道且高效的原地快速排序算法。这个实现充分利用了Go语言切片引用的特性、简洁的元素交换语法以及递归机制,体现了Go语言在处理数据结构和算法方面的优雅与强大。理解这些Go语言特有的处理方式,对于编写高性能和符合Go语言习惯的代码至关重要。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

713

2023.08.22

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

529

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

12

2025.12.22

Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

233

2023.09.06

go怎么实现链表
go怎么实现链表

go通过定义一个节点结构体、定义一个链表结构体、定义一些方法来操作链表、实现一个方法来删除链表中的一个节点和实现一个方法来打印链表中的所有节点的方法实现链表。

442

2023.09.25

go语言编程软件有哪些
go语言编程软件有哪些

go语言编程软件有Go编译器、Go开发环境、Go包管理器、Go测试框架、Go文档生成器、Go代码质量工具和Go性能分析工具等。本专题为大家提供go语言相关的文章、下载、课程内容,供大家免费下载体验。

246

2023.10.13

0基础如何学go语言
0基础如何学go语言

0基础学习Go语言需要分阶段进行,从基础知识到实践项目,逐步深入。php中文网给大家带来了go语言相关的教程以及文章,欢迎大家前来学习。

691

2023.10.26

Go语言实现运算符重载有哪些方法
Go语言实现运算符重载有哪些方法

Go语言不支持运算符重载,但可以通过一些方法来模拟运算符重载的效果。使用函数重载来模拟运算符重载,可以为不同的类型定义不同的函数,以实现类似运算符重载的效果,通过函数重载,可以为不同的类型实现不同的操作。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

191

2024.02.23

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

149

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.2万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号