尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

hello-algo 选择排序详解:O(n²) 原地排序的原理、多语言实现与稳定性分析

hello-algo 选择排序详解:O(n²) 原地排序的原理、多语言实现与稳定性分析 hello-algo 选择排序详解O(n²) 原地排序的原理、多语言实现与稳定性分析【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo选择排序Selection Sort是《Hello 算法》排序篇讲解的最基础排序算法之一每轮从未排序区间选出最小元素交换到已排序区间的末尾经过n-1轮即可完成整个数组的排序。本文基于仓库文档 selection_sort.md 的算法流程结合 codes/python/chapter_sorting/selection_sort.py、codes/java/chapter_sorting/selection_sort.java 等多语言源码实现与 codes/go/chapter_sorting/selection_sort_test.go 测试用例深入讲解选择排序的完整算法流程、代码细节、时间/空间复杂度与不稳定性成因帮助读者彻底掌握这一原地排序算法的底层逻辑与适用边界。算法核心思想未排序区间逐轮缩小选择排序的工作原理非常直观开启一个循环每轮从未排序区间中挑选出最小的元素将其放到已排序区间的末尾。设数组长度为n整个算法流程分为 5 个阶段初始状态所有元素未排序即未排序索引区间为[0, n-1]。第一轮选取区间[0, n-1]中的最小元素将其与索引0处的元素交换。完成后数组前 1 个元素已排序。第二轮选取区间[1, n-1]中的最小元素将其与索引1处的元素交换。完成后数组前 2 个元素已排序。依此类推每轮扫描的起点右移一位。经过n-1轮选择与交换后数组前n-1个元素已排序。收尾仅剩的最后一个元素必定是最大元素无须排序因此数组排序完成。从源码结构看这一思想在多语言实现中被统一抽象为「外层循环定位已排序区间边界i、内层循环在未排序区间[i1, n-1]内寻找最小值索引k、最后交换nums[i]与nums[k]」的固定模式各语言版本仅交换写法不同Python/Go 使用元组交换C/Java 使用临时变量。Python 参考实现仓库中的 Python 参考实现位于 selection_sort.py共约 26 行结构清晰、可直接运行def selection_sort(nums: list[int]): 选择排序 n len(nums) # 外循环未排序区间为 [i, n-1] for i in range(n - 1): # 内循环找到未排序区间内的最小元素 k i for j in range(i 1, n): if nums[j] nums[k]: k j # 记录最小元素的索引 # 将该最小元素与未排序区间的首个元素交换 nums[i], nums[k] nums[k], nums[i] Driver Code if __name__ __main__: nums [4, 1, 3, 1, 5, 2] selection_sort(nums) print(选择排序完成后 nums , nums)逐行对照算法流程可以确认三个关键细节外循环边界是n - 1而非nfor i in range(n - 1)对应流程中的「n-1轮选择与交换」最后一轮扫描结束后剩余的唯一元素自然落在正确位置因此不需要第n轮。内循环起点是i 1nums[i]本身已是未排序区间的候选最小值所以从k i初始化后只需扫描[i1, n-1]if nums[j] nums[k]严格小于的写法保证了相等元素不会被提前记录为后文分析不稳定性埋下伏笔。每轮至多一次交换与冒泡排序每轮可能多次交换不同选择排序在内层循环中只记录索引k循环结束后才执行一次swap这是其交换次数上界仅为n-1的原因。仓库中 C 语言版本 selection_sort.c 提供了无临时对象的原地实现可对比查看/* 选择排序 */ void selectionSort(int nums[], int n) { // 外循环未排序区间为 [i, n-1] for (int i 0; i n - 1; i) { // 内循环找到未排序区间内的最小元素 int k i; for (int j i 1; j n; j) { if (nums[j] nums[k]) k j; // 记录最小元素的索引 } // 将该最小元素与未排序区间的首个元素交换 int temp nums[i]; nums[i] nums[k]; nums[k] temp; } }C 版本中main函数以{4, 1, 3, 1, 5, 2}为样例数组调用selectionSort(nums, n)并借助 codes/c/utils/print_util.h 中的printArray打印结果与 Python 版本的驱动数据完全一致便于跨语言对照验证。此外仓库还完整提供了 C 实现、Java 实现、Go 实现、C#、JavaScript、Rust 等十余种语言的参考代码均遵循同一套「双循环 索引 k」的骨架。测试用例验证从样例数据看运行结果Go 语言目录中附带了可执行的测试文件 selection_sort_test.gofunc TestSelectionSort(t *testing.T) { nums : []int{4, 1, 3, 1, 5, 2} selectionSort(nums) fmt.Println(选择排序完成后 nums , nums) }该测试与各语言 Driver Code 使用同一样例[4, 1, 3, 1, 5, 2]。按算法流程手动推演第 1 轮在[4,1,3,1,5,2]中选出最小值1索引 1与nums[0]交换得到[1,4,3,1,5,2]第 2 轮选出1索引 3交换后得到[1,1,3,4,5,2]第 3 轮选出2交换得到[1,1,2,4,5,3]第 4 轮选出3交换得到[1,1,2,3,5,4]第 5 轮选出4交换得到[1,1,2,3,4,5]。全部n-1 5轮结束后数组有序运行go test即可在 codes/go 目录下复现该验证过程。注意样例中保留了重复元素1恰好可以观察不稳定性见下文分析。算法特性分析时间复杂度 O(n²)非自适应排序外循环共执行n-1轮第一轮内循环执行n-1次最后一轮执行 1 次即各轮内循环分别执行n-1、n-2、…、2、1 次求和为$$\frac{n(n-1)}{2}$$因此时间复杂度恒为O(n²)。关键在于内层循环的轮次只取决于数组长度n与输入数据的初始分布无关——即使数组已经有序算法仍会完整执行全部比较。这种「性能不随输入状态变化」的特点正是非自适应排序的定义也意味着选择排序无法利用近乎有序的输入数据来降低开销。空间复杂度 O(1)原地排序从 selection_sort.py 的实现结构看函数体内仅声明了n、i、j、k四个循环/索引变量交换操作直接在原数组上完成没有引入与n相关的额外数据结构。因此额外空间为常数级O(1)属于原地排序in-place sort。这也是选择排序在内存受限场景下的主要优势。非稳定排序交换可能打乱相等元素的相对顺序稳定性指相等元素在排序后是否保持原有的相对先后顺序。选择排序是非稳定排序原因可以从源码中if nums[j] nums[k]与「整轮结束后统一交换」这两个细节推导出来内层循环只更新索引k而不提前交换当最小值出现在未排序区间深处时它与nums[i]的交换会跨越若干元素其中可能包含与nums[i]相等的元素从而改变它们的相对顺序。以文档给出的非稳定示例说明若某轮中nums[i]处的2需要与更深位置的2交换交换后先出现的2反而排到了后出现的2右侧相等元素的相对顺序被破坏。这也解释了为何在对稳定性有要求的场景例如多关键字排序中应选择插入排序等稳定算法而不是选择排序。小结选择排序的适用定位综合以上分析选择排序的核心特性可以归纳为特性结论说明时间复杂度O(n²)最好/最坏/平均非自适应与输入分布无关空间复杂度O(1)原地排序仅需常数额外空间稳定性不稳定跨区间的单次交换可能打乱相等元素顺序交换次数至多n-1次每轮内循环结束后仅执行一次交换选择排序实现简单、交换次数少适合小规模数据或对交换操作代价敏感的场景而对于大规模数据建议参考同章的 快速排序、归并排序 或 堆排序 等O(n log n)算法。完整的跨语言对照实现可在 codes/python/chapter_sorting、codes/java/chapter_sorting、codes/go/chapter_sorting 等目录中按语言目录查阅。【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表