数据结构:选择排序 (Selection Sort)

目录

从学生排队开始

算法的初始状态和核心操作

代码的逐步完善

第一阶段:定义函数框架和外层循环

第二阶段:实现“寻找最小元素”的逻辑(内层循环)

第三阶段:完成“交换”操作

复杂度与特性分析

时间复杂度 (Time Complexity)

空间复杂度 (Space Complexity)

稳定性 (Stability)


从学生排队开始

我们已经探讨了两种排序思路:

  1. 冒泡排序:不断比较相邻的元素,像“本地微调”,慢慢把大的元素换到后面。

  2. 插入排序:始终维护一个有序的局部,然后把新元素插入进去。

现在我们思考一种完全不同的、更具“全局观”的策略。想象一下,老师让一队学生按身高从矮到高排队。老师会怎么做?

一种非常直接的方法是:

  1. 老师先扫视所有未排队的学生,从中找出最矮的那一个。

  2. 然后指着队伍的最前面说:“你,站到这里来。”

  3. 接着,老师在剩下的学生中,再扫视一遍,找出剩下人里最矮的。

  4. 然后指着队伍的第二个位置说:“你,站到这里来。”

  5. 重复这个过程,直到所有学生都排好队。

这个“每次选择一个最值,放到它最终应该在的位置”的思路,就是选择排序的灵魂。


算法的初始状态和核心操作

和插入排序一样,我们也将数组在逻辑上分为两个部分:

  1. 左边是已经排好序、元素都已在最终位置的有序区。

  2. 右边是还未处理、元素位置都是临时的无序区。

算法开始时,有序区是空的,整个数组都是无序区。

📌 核心操作(一轮迭代)

  1. 在无序区中,通过一次完整的扫描,找到值最小的那个元素。

  2. 将这个最小的元素与无序区的第一个元素交换位置。

  3. 交换完成后,无序区的第一个元素就变成了有序区的最后一个元素。有序区长度加一,无序区长度减一。

我们来手动模拟一下。假设数组是 arr = [64, 25, 12, 22, 11]

  • 有序区: []

  • 无序区: [64, 25, 12, 22, 11]

1️⃣ 第一轮 (i=0):

  1. 扫描无序区 [64, 25, 12, 22, 11]

  2. 发现最小的元素是 11,它的索引是 4

  3. 11 与无序区的第一个元素 64 交换。

  •  结果:[**11**, 25, 12, 22, 64]。现在 11 已经在它的最终位置了。

  • 有序区: [11], 无序区: [25, 12, 22, 64]

2️⃣ 第二轮 (i=1):

  1. 扫描无序区 [25, 12, 22, 64]

  2. 发现最小的元素是 12,它的索引是 2

  3. 12 与无序区的第一个元素 25 交换。

  • 结果:[11, **12**, 25, 22, 64]。现在 12 也归位了。

  • 有序区: [11, 12], 无序区: [25, 22, 64]

这个过程持续 n-1 轮,当 n-1 个元素都归位后,剩下的最后一个元素自然也在正确的位置上了。


代码的逐步完善

现在,我们将这个“选择-交换”的策略翻译成代码。

第一阶段:定义函数框架和外层循环

外层循环的职责是控制轮次,也就是每次为哪个位置寻找正确的元素。

i 将代表当前“坑位”的索引,我们要在无序区中找到元素来填这个“坑”。

#include <iostream>void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// 选择排序函数框架
void selectionSort(int arr[], int n) {// 外层循环控制轮次,i 是当前要放置正确元素的目标位置// 我们需要为 arr[0], arr[1], ..., arr[n-2] 依次寻找元素// 当 arr[n-2] 也正确时,arr[n-1] 必然也正确了for (int i = 0; i < n - 1; ++i) {// 在这里,我们将找到无序区 arr[i...n-1] 中的最小元素,// 然后把它放到 arr[i] 这个位置上。}
}

第二阶段:实现“寻找最小元素”的逻辑(内层循环)

在每一轮中,我们需要一个内层循环来扫描整个无序区,目的是找到最小元素的索引。

void selectionSort(int arr[], int n) {for (int i = 0; i < n - 1; ++i) {// 1. 先假设当前无序区的第一个元素就是最小的int min_idx = i;// 2. 用内层循环扫描无序区的其他元素for (int j = i + 1; j < n; ++j) {// 如果发现了更小的元素...if (arr[j] < arr[min_idx]) {// ...就更新最小元素的索引min_idx = j;}}// 当这个内层循环结束后,min_idx 就一定指向了// 整个无序区中最小元素的实际位置。// 接下来就是交换操作了。}
}

第三阶段:完成“交换”操作

找到了最小元素的索引 min_idx 后,我们只需要将它和当前轮次的目标位置 i 的元素进行一次交换。这个交换操作发生在内层循环结束之后。

void printArray(int arr[], int size) {for (int i = 0; i < size; i++) {std::cout << arr[i] << " ";}std::cout << std::endl;
}// 最终的选择排序代码
void selectionSort(int arr[], int n) {for (int i = 0; i < n - 1; ++i) {// 寻找 [i, n-1] 区间里的最小元素int min_idx = i;for (int j = i + 1; j < n; ++j) {if (arr[j] < arr[min_idx]) {min_idx = j;}}// 将找到的最小元素与 i 位置的元素交换swap(&arr[min_idx], &arr[i]);std::cout << "第 " << i + 1 << " 轮排序后: ";printArray(arr, n);}
}

至此,一个从“全局选择最优”这个第一性原理出发的选择排序算法就清晰地构建完成了。


复杂度与特性分析

我们用之前建立的评判标准来分析它。

时间复杂度 (Time Complexity)

  • 算法的主要耗时在于嵌套的循环。

    • 外层循环执行 n-1 次。

    • 内层循环的执行次数:

      • i=0 时,比较 n-1 次。

      • i=1 时,比较 n-2 次。

      • ...

      • i=n-2 时,比较 1 次。

  • 总的比较/移动次数大约是 1 + 2 + ... + (n−1) = n * (n−1) / 2。

⚠️ 请注意,无论输入的数组是已经有序、还是完全逆序,选择排序的这个比较次数是固定不变的

因为它必须完整地扫描完无序区,才能确认哪个是最小的。它无法像插入排序或优化后的冒泡排序那样提前结束。

因此,选择排序的最好、最坏和平均情况的时间复杂度都是:O(n^2)


空间复杂度 (Space Complexity)

  • 在排序过程中,我们只使用了 i, j, min_idx 这几个辅助变量。

  • 变量数量是固定的常数,不随 n 的增长而增长。

  • 因此,空间复杂度是:O(1)

  • 它也是一个原地排序 (In-place Sort) 算法。


稳定性 (Stability)

  • 这是选择排序一个非常重要的特性。我们来举个例子判断一下。

  • 假设数组是 [5a, 8, 5b, 2] (这里 5a5b 值相等,但我们为了区分,加上了标记)。

  • 第一轮 (i=0)

    1. 扫描整个数组,发现最小元素是 2

    2. 2 与无序区的第一个元素 arr[0] (也就是 5a) 进行交换。

    3. 交换后,数组变为 [2, 8, 5b, 5a]

  • 观察结果:在原始数组中,5a5b 的前面。但在第一轮排序后,5a 跑到了 5b 的后面。它们的相对位置发生了改变。

  • 仅仅一个反例就足以证明,选择排序不是一个稳定的排序算法。

  • 因此,选择排序是❌不稳定排序 (Unstable Sort)


选择排序的特点非常鲜明:它的时间复杂度很“死板”,不受输入数据的影响,始终是 O(n^2)。

但它有一个优点,就是数据交换的次数非常少,最多只有 n-1 次。在一些“写”操作远比“读”操作昂贵的场景下,这可能成为一个考虑因素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95189.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django Admin 管理工具

一、简介Django Admin 是 Django 框架最受欢迎和强大的特性之一。它是一个自动生成的管理后台&#xff0c;允许开发者无需或仅需编写少量代码&#xff0c;就能对网站的数据模型&#xff08;数据库中的表&#xff09;进行直观的增、删、改、查&#xff08;CRUD&#xff09;操作。…

园区智慧水电管理系统:让能源管理从“成本黑洞”变“利润引擎”

园区智慧水电管理系统&#xff0c;是一套专为产业园区、科技园、企业总部等大型空间设计的集智能计量、远程管控、自动计费、能耗分析于一体的数字化能源解决方案。它用技术手段解决水电管理中的“抄表难、收费乱、浪费多、数据缺”四大顽疾&#xff0c;真正实现降本、提效、控…

DeepSeek应用技巧-通过MCP打造数据分析助手

本文章将通过MCP服务来打造一个数据分析助手&#xff0c;可以直接读取本地的excel或csv的文件&#xff0c;然后生成可视化的报告并保存在本地&#xff0c;十分有应用和实践的价值&#xff0c;话不多说&#xff0c;我们开始手把手搭建。一、知识应用&#xff08;1&#xff09;Fu…

React Hooks 完全指南:从基础到高级的实战技巧

概述 React Hooks 是 React 16.8 引入的新特性&#xff0c;允许在函数组件中使用状态和其他 React 特性。根据数据的使用场景和更新机制&#xff0c;可以将 Hooks 分为三大类&#xff1a; 1. 保存只读数据 useMemo 用途&#xff1a; 缓存计算结果&#xff0c;避免重复计算 …

PCIe 6.0 vs 5.0:带宽翻倍背后的技术革命

PCIe 6.0 vs 5.0&#xff1a;带宽翻倍背后的技术革命在数据中心、AI计算和高速存储需求爆炸式增长的今天&#xff0c;传统接口带宽已成为系统性能提升的瓶颈。PCIe 6.0的推出正是为了解决这一挑战&#xff0c;它通过革命性的技术创新&#xff0c;在保持向后兼容的同时实现了带宽…

突破传统企业组网瓶颈:某科技公司智能组网服务项目深度解析

在现代企业的数字化转型过程中&#xff0c;稳定、高效、安全的网络基础设施已成为业务发展的关键。然而&#xff0c;传统组网方案往往面临诸多挑战&#xff0c;如网络性能不足、组网复杂度高、扩展性不佳、以及安全防护薄弱等问题。为了解决这些痛点&#xff0c;某科技公司通过…

ubuntu单机实现10000个连接同时在线测试

连接前 成功连接后 前端测试连接脚本: c_5k.sh !/bin/bash ulimit -n 100000 # client_simulator.sh SERVER_IP="192.168.0.106" SERVER_PORT=8080 MAX_CLIENTS=5000 BATCH_SIZE=100echo "Starting $MAX_CLIENTS clients to $SERVER_IP:$SERVER_PORT"…

防护墙技术(一):NAT

###源NAT基本原理 NAT&#xff08;Network Address Translation&#xff09;网络地址转换技术 源NAT技术对IP报文的源地址进行转换&#xff0c;将私有IP地址转换为公网IP地址&#xff0c;使大量私网用户可以利用少量公网IP地址访问internet&#xff0c;大大减少对公网IP的消耗 …

动态规划2(c++)

酒鬼#include <bits/stdc.h> using namespace std; int main() {int n;cin>>n;int a[10010];for(int i 1;i<n;i){cin>>a[i];}int dp[1010][5] {0};dp[0][0] 0;dp[1][0] 0;dp[1][1] a[1];dp[1][2] 0;dp[2][0] a[1];dp[2][1] a[2];dp[2][2] a[1]a[…

「LangChain 学习笔记」LangChain大模型应用开发:代理 (Agent)

「LangChain大模型应用开发」 系列文章目录&#xff1a; LangChain大模型应用开发&#xff1a;模型&#xff0c;提示和输出解释器 LangChain大模型应用开发&#xff1a;储存(Memory) LangChain大模型应用开发&#xff1a;模型链&#xff08;Chains&#xff09; LangChain大模…

python pyqt5开发DoIP上位机【介绍】

目录文章合集一、核心功能概述二、主要模块解析1. 导入的库2. 辅助函数3. DOIP协议处理&#xff08;DOIPProtocol类&#xff09;4. 网络工具&#xff08;NetworkUtils类&#xff09;5. 通信线程&#xff08;DOIPCommunicationThread类&#xff09;6. UDS命令输入组件&#xff0…

从零实现一个可扩展的规则解析引擎 —— 支持 AND/OR 优先级、短路求值与多类型运算符

在日常业务开发中&#xff0c;我们经常需要基于一些“规则”来决定程序的走向。比如&#xff1a; 客服机器人 根据用户问题领域和复杂度选择不同的模型&#xff1b;营销系统 根据用户画像匹配不同优惠券&#xff1b;风控引擎 根据请求参数、时间、分值判定是否放行。 这些规则往…

Preprocessing Model in MPC 3 - 基于同态加密的协议 - Over Rings 环

参考论文&#xff1a;SoK: Multiparty Computation in the Preprocessing Model MPC (Secure Multi-Party Computation) 博士生入门资料。抄袭必究。 本系列教程将逐字解读参考论文(以下简称MPCiPPM)&#xff0c;在此过程中&#xff0c;将论文中涵盖的40篇参考文献进行梳理与讲…

uni-app 跨平台项目的 iOS 上架流程:多工具组合的高效协作方案

跨平台框架的兴起&#xff0c;让许多团队选择 uni-app 来开发移动应用。 一套代码多端运行&#xff0c;确实大大降低了研发成本&#xff0c;但当项目进入 iOS 上架阶段 时&#xff0c;很多团队依旧面临挑战&#xff1a;证书复杂、环境不统一、上传繁琐。 本文结合实战经验&…

掌握 Linux 文件权限:chown 命令深度解析与实践

在 Linux 系统的日常运维与开发工作里&#xff0c;文件权限管理是保障系统安全、规范文件访问的关键环节。其中&#xff0c;chown 命令作为修改文件所有者及关联组的核心工具&#xff0c;对精准把控文件权限起着重要作用。接下来&#xff0c;我们将全面拆解 chown 命令&#xf…

计算机算术7-浮点基础知识

1. 浮点表示其中b表示基底&#xff0c;e表示指数&#xff0c;s表示尾数&#xff0c;注意在s的表示过程中&#xff0c;有个隐藏1.同时还有个符号位从下面这个图可以看出&#xff0c;向上溢出和向下溢出的概念&#xff0c;overflow表示的是数的绝对值超过了最大的表示范围&#x…

设计模式8-命令模式

定义 Command Partern: 将一个请求封装成一个对象&#xff0c;从而让你使用不同的请求把客户端参数化&#xff0c;对请求排队或者记录请求日志&#xff0c;可以提供命令的撤销和恢复功能。&#xff08;核心思想是将“动作”与“执行者”解耦&#xff09; 场景 GUI&#xff1a;…

数据结构(顺序表力扣刷题)

1.移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操作&…

机器学习 - Kaggle项目实践(6)Dogs vs. Cats Redux: Kernels Edition 猫狗二分类

Dogs vs. Cats Redux: Kernels Edition | Kaggle 任务&#xff1a;给定猫狗图像数据集 进行二分类。 Cats or Dogs - using CNN with Transfer Learning | Kaggle&#xff08;参考&#xff09; Cats or Dogs | Kaggle &#xff08;我的kaggle&#xff09; 本文介绍了使用Re…

基础的汇编指令

目录 1、接上一个csdn特殊功能寄存器 1.1CPSR寄存器 1.2SPSR寄存器 1.3CPSR寄存器的高四位和第四位 ​编辑 2、汇编指令的分类 3、汇编指令的基本格式 4、数据搬移指令&#xff08;赋值指令&#xff09; 4.1指令码 4.2指令格式 4.3测试代码 4.5立即数 4.6ldr伪指令 …